atlas: repos panel — surface the user's own Gitea repos, mirroring the shipped issues slice #10

Closed
opened 2026-08-18 12:47:24 +00:00 by mathias · 10 comments
Owner

Split out of #4's "Not yet shipped" list so it can be worked as one self-contained change. #4 stays open as the umbrella.

Goal

Show the user's own Gitea repos in the atlas, the same way open issues already appear on stage 03.

The pattern to mirror — already in this repo

The issues slice (b2761a4) is the template. Follow it structurally rather than inventing a new approach:

Layer Existing (issues) To add (repos)
Pure parser internal/atlas/issues.go → IssueNodes() RepoNodes() — Gitea repo list → stage nodes
API client internal/gitea/client.go → MyIssues() MyRepos() — authenticated GET, GITEA_TOKEN
Wiring internal/web/handler.go → liveOverlay() same function, onto the appropriate stage
Config generate:"gitea-issues" in atlas.json an equivalent generate: key for repos

Acceptance criteria

  • RepoNodes() is a pure function over an already-decoded API response, unit-tested table-driven — no network in the test. Match issues.go's test style.
  • Each node carries the repo name, a short description or tag, and a clickable html_url (the frontend already renders any node with a url as <a class="node">).
  • MyRepos() returns an error when GITEA_TOKEN is unset, and the overlay skips gracefully — the atlas must still render with its authored placeholder nodes, exactly like the issues path does today. This is load-bearing: GITEA_TOKEN is still not provisioned in prod (#4), so the unset path is the one that runs live right now.
  • Archived repos are excluded.
  • task check green (lint, vet, race tests).

Out of scope

  • PRs panel and the per-repo Actions-runs generalization — separate bullets on #4, don't pull them in here.
  • Anything under mathias/infra, including the GITEA_TOKEN ExternalSecret. That needs a real PAT and a human; do not attempt it, and do not work around its absence with a hardcoded token.

Notes

Read-only only. The token is Mathias's own PAT, so Gitea's own visibility rules already scope what can be seen — do not add any filtering that assumes otherwise.

Split out of #4's "Not yet shipped" list so it can be worked as one self-contained change. #4 stays open as the umbrella. ## Goal Show the user's own Gitea repos in the atlas, the same way open issues already appear on stage 03. ## The pattern to mirror — already in this repo The issues slice (b2761a4) is the template. Follow it structurally rather than inventing a new approach: | Layer | Existing (issues) | To add (repos) | |---|---|---| | Pure parser | `internal/atlas/issues.go` → `IssueNodes()` | `RepoNodes()` — Gitea repo list → stage nodes | | API client | `internal/gitea/client.go` → `MyIssues()` | `MyRepos()` — authenticated GET, `GITEA_TOKEN` | | Wiring | `internal/web/handler.go` → `liveOverlay()` | same function, onto the appropriate stage | | Config | `generate:"gitea-issues"` in `atlas.json` | an equivalent `generate:` key for repos | ## Acceptance criteria - [ ] `RepoNodes()` is a pure function over an already-decoded API response, unit-tested table-driven — no network in the test. Match `issues.go`'s test style. - [ ] Each node carries the repo name, a short description or tag, and a clickable `html_url` (the frontend already renders any node with a `url` as `<a class="node">`). - [ ] `MyRepos()` returns an error when `GITEA_TOKEN` is unset, and the overlay **skips gracefully** — the atlas must still render with its authored placeholder nodes, exactly like the issues path does today. This is load-bearing: `GITEA_TOKEN` is still not provisioned in prod (#4), so the unset path is the one that runs live right now. - [ ] Archived repos are excluded. - [ ] `task check` green (lint, vet, race tests). ## Out of scope - PRs panel and the per-repo Actions-runs generalization — separate bullets on #4, don't pull them in here. - Anything under `mathias/infra`, including the `GITEA_TOKEN` ExternalSecret. That needs a real PAT and a human; do not attempt it, and do not work around its absence with a hardcoded token. ## Notes Read-only only. The token is Mathias's own PAT, so Gitea's own visibility rules already scope what can be seen — do not add any filtering that assumes otherwise.
mathias added the status:dispatchstatus:dispatched labels 2026-08-18 12:48:00 +00:00
Author
Owner

🤖 dispatch: routing: executor=claude (default — no executor: label) — starting headless run

🤖 dispatch: routing: executor=claude (default — no executor:<name> label) — starting headless run
Author
Owner

🤖 dispatch: executor claude failed: runner error: agent run ended in error (see stderr)

🤖 dispatch: executor claude failed: runner error: agent run ended in error (see stderr)
mathias added the status:redispatch label 2026-08-18 12:57:54 +00:00
Author
Owner

🤖 dispatch: routing: executor=claude (default — no executor: label) — starting headless run

🤖 dispatch: routing: executor=claude (default — no executor:<name> label) — starting headless run
Author
Owner

🤖 dispatch: executor claude failed: runner error: agent run ended in error (see stderr)

🤖 dispatch: executor claude failed: runner error: agent run ended in error (see stderr)
Author
Owner

🤖 dispatch: routing: executor=claude (default — no executor: label) — starting headless run

🤖 dispatch: routing: executor=claude (default — no executor:<name> label) — starting headless run
Author
Owner

🤖 dispatch: executor claude failed: runner error: agent run ended in error (see stderr)

🤖 dispatch: executor claude failed: runner error: agent run ended in error (see stderr)
Author
Owner

🤖 dispatch: routing: executor=claude (default — no executor: label) — starting headless run

🤖 dispatch: routing: executor=claude (default — no executor:<name> label) — starting headless run
Author
Owner

🤖 dispatch: executor claude failed: runner error: agent run ended in error (see stderr)

🤖 dispatch: executor claude failed: runner error: agent run ended in error (see stderr)
mathias removed the status:redispatchstatus:dispatch labels 2026-08-18 13:00:28 +00:00
Author
Owner

Dispatched 2026-08-18 12:47 and again at 12:58 (status:redispatch). Both runs failed in ~3 seconds, before turn 1 — nothing to do with this issue's scope.

Root cause is dispatch-wide and now tracked in dispatch#33: the Anthropic account has no credit (Credit balance is too low from the CLI in the dispatch image, while the API key itself is valid — /v1/models returns 200). Every dispatched run since 2026-07-03 has failed this way.

Labels cleared (status:dispatch, status:redispatch removed) so this issue is not stuck re-firing every minute. status:dispatched left on to record that it was attempted.

To pick this up once credit is topped up: apply status:redispatch — plain status:dispatch will not re-trigger, because the bot comments above already mark it handled.

The scope in the issue body is unchanged and still accurate.

Dispatched 2026-08-18 12:47 and again at 12:58 (`status:redispatch`). Both runs failed in ~3 seconds, before turn 1 — nothing to do with this issue's scope. Root cause is dispatch-wide and now tracked in dispatch#33: the Anthropic account has no credit (`Credit balance is too low` from the CLI in the dispatch image, while the API key itself is valid — `/v1/models` returns 200). Every dispatched run since 2026-07-03 has failed this way. Labels cleared (`status:dispatch`, `status:redispatch` removed) so this issue is not stuck re-firing every minute. `status:dispatched` left on to record that it was attempted. To pick this up once credit is topped up: apply `status:redispatch` — plain `status:dispatch` will not re-trigger, because the bot comments above already mark it handled. The scope in the issue body is unchanged and still accurate.
mathias added the risk/lowsize/stype/feat labels 2026-08-27 05:25:38 +00:00
mathias added the agent/ready label 2026-08-27 05:28:17 +00:00
mathias added the status:dispatch label 2026-08-27 05:29:33 +00:00
mathias removed the status:dispatch label 2026-08-27 05:31:17 +00:00
mathias added the executor:gemmastatus:redispatch labels 2026-08-27 05:49:39 +00:00
mathias removed the status:redispatch label 2026-08-27 05:56:39 +00:00
Author
Owner

Shipped in 097d874, released as v0.8.0. CI green on main (run #71) and on the tag (run #72).

Acceptance criteria

  • RepoNodes() is a pure function over an already-decoded API response, unit-tested, no network in the test. internal/atlas/repos.go, tests in repos_test.go — five cases, matching issues_test.go's style.
  • Each node carries the repo name, a short description or tag, and a clickable html_url. Title = repo name, Desc = description, Tags = ["live · Gitea", <language>], URL = html_url.
  • MyRepos() errors when GITEA_TOKEN is unset, and the overlay skips gracefully. Both halves tested: client_test.go covers the error, and TestPrependGenerated_NoLiveNodesLeavesAuthoredIntact covers the authored nodes surviving an empty overlay. Verified live: with no token the atlas returns 200 with stage 05's authored nodes untouched.
  • Archived repos are excluded. TestRepoNodes_ExcludesArchived.
  • task check green — golangci-lint 0 issues, vet, -race.

Out-of-scope items respected: no PRs panel, no per-repo Actions generalization, nothing touched under mathias/infra, no hardcoded token.

One design decision that wasn't in the ticket

The issue said "onto the appropriate stage". Stage 05 is the right home — repos are what the execution stage acts on — but #11 landed first and claimed stage 05's generate key for stuck-badges, and a stage could only name one source.

Rather than displace #11 or park repos on a semantically wrong free stage (00/01/02 were the only ones left), generate now accepts either spelling:

"generate": "ci-jobs"
"generate": ["stuck-badges", "gitea-repos"]

Bare strings stay valid and marshal back as bare strings, so no authored stage was rewritten and the frontend contract is unchanged. Stages 04 and 08 already shared stuck-badges between them — this is the same growth one level in. Verified live: stage 05 now serves both overlays at once (stuck badges → 8 live repos → 8 authored nodes).

Incidental fix

The overlay merge moved out of handler.go into atlas.PrependGenerated, which is unit-testable where the inline loop was not — it sat behind a network call. That fixed a latent aliasing bug in the issues path: the old append(liveNodes, authored...) wrote into the live slice's backing array, so two stages sharing a generate key would corrupt each other. Not hypothetical any more, now that stuck-badges spans three stages.

Not fixed here

  • /user/repos?limit=8 returns the alphabetically first 8 repos (agentsquad … brain-weekly-ingest), which is arbitrary for a live view. Sorting by recent update would be more useful — one query param, but it's a behaviour change the ticket didn't ask for, so left alone.
  • Filed #13: CI installs templ@latest while go.mod pins v0.3.1020.
  • GITEA_TOKEN is still unprovisioned in prod, so this stays inert there until #4's ExternalSecret lands.
Shipped in `097d874`, released as **v0.8.0**. CI green on `main` ([run #71](https://git.d-ma.be/mathias/cad-atlas/actions/runs/71)) and on the tag ([run #72](https://git.d-ma.be/mathias/cad-atlas/actions/runs/72)). ## Acceptance criteria - [x] **`RepoNodes()` is a pure function over an already-decoded API response, unit-tested, no network in the test.** `internal/atlas/repos.go`, tests in `repos_test.go` — five cases, matching `issues_test.go`'s style. - [x] **Each node carries the repo name, a short description or tag, and a clickable `html_url`.** Title = repo name, `Desc` = description, `Tags` = `["live · Gitea", <language>]`, `URL` = `html_url`. - [x] **`MyRepos()` errors when `GITEA_TOKEN` is unset, and the overlay skips gracefully.** Both halves tested: `client_test.go` covers the error, and `TestPrependGenerated_NoLiveNodesLeavesAuthoredIntact` covers the authored nodes surviving an empty overlay. Verified live: with no token the atlas returns 200 with stage 05's authored nodes untouched. - [x] **Archived repos are excluded.** `TestRepoNodes_ExcludesArchived`. - [x] **`task check` green** — golangci-lint 0 issues, vet, `-race`. Out-of-scope items respected: no PRs panel, no per-repo Actions generalization, nothing touched under `mathias/infra`, no hardcoded token. ## One design decision that wasn't in the ticket The issue said "onto the appropriate stage". Stage 05 is the right home — repos are what the execution stage acts on — but #11 landed first and claimed stage 05's `generate` key for `stuck-badges`, and a stage could only name one source. Rather than displace #11 or park repos on a semantically wrong free stage (00/01/02 were the only ones left), `generate` now accepts either spelling: ```json "generate": "ci-jobs" "generate": ["stuck-badges", "gitea-repos"] ``` Bare strings stay valid and marshal back as bare strings, so no authored stage was rewritten and the frontend contract is unchanged. Stages 04 and 08 already shared `stuck-badges` between them — this is the same growth one level in. Verified live: stage 05 now serves both overlays at once (stuck badges → 8 live repos → 8 authored nodes). ## Incidental fix The overlay merge moved out of `handler.go` into `atlas.PrependGenerated`, which is unit-testable where the inline loop was not — it sat behind a network call. That fixed a latent aliasing bug in the issues path: the old `append(liveNodes, authored...)` wrote into the live slice's backing array, so two stages sharing a `generate` key would corrupt each other. Not hypothetical any more, now that `stuck-badges` spans three stages. ## Not fixed here - `/user/repos?limit=8` returns the **alphabetically** first 8 repos (`agentsquad` … `brain-weekly-ingest`), which is arbitrary for a live view. Sorting by recent update would be more useful — one query param, but it's a behaviour change the ticket didn't ask for, so left alone. - Filed #13: CI installs `templ@latest` while `go.mod` pins `v0.3.1020`. - `GITEA_TOKEN` is still unprovisioned in prod, so this stays inert there until #4's ExternalSecret lands.
This repo is archived. You cannot comment on issues.
1 Participants
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mathias/cad-atlas#10