Unified searchable select + three-level agent-CLI selection (global/repo/spawn) with skip-layer resolution #66
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab#66
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
Four operator-visible problems on the spawn surfaces, one structural gap behind them:
.composer-pop-panelhard-codes open-above (web/src/base.css—bottom: calc(100% + 6px), fixedmax-height: 15rem) while the composer sits near the top of the page, so with many repos the panel's scroll region is unreachable above the viewport (worst on phone).<select>chips. No typeahead anywhere.RepoSettings.tsx) while the AFK section directly below already usesCatalogSelect.repos.provideris stamped once fromreposvc.DefaultProvider;PATCH /repos/{id}rejectsprovider; AddRepo never offers it.ResolveModelEffort400s any resolved value missing from the provider's catalog (internal/instance/credential.go). The seeded global default isopus[1m](claude-shaped): the moment a second provider exists, an AFK run on its repo resolves the claude default and every launch 400s. A provider with an empty efforts catalog can never spawn (HasOption([], "") == false).The model/effort catalog seam itself already exists and stays as-is:
AgentProvider.Models()/Efforts()are provider-owned pinned catalogs served onGET /api/v1/providers(ADR-0021/D14); the start screen already re-catalogs by the selected repo's provider. Codex (#2) just declares its lists.Decisions (grilled with the operator 2026-07-09 — do not re-litigate)
Provider selection — three levels, D12d-style layering
repos.providerbecomes nullable (NULL = inherit); new seeded settingprovider_default = claude-code. One migration.repos.afk_provider_default(nullable) + globalspawn_provider_default_afk(empty = inherit), resolved AFK-first exactly like model/effort (ADR-0021). The AFK model/effort selects in both settings pages render against the AFK-effective provider's catalog and re-catalog when the AFK provider select changes.POST .../instances(start) accepts an optionalprovider;PATCH /repos/{id}acceptsproviderandafk_provider_default. Run rows already storeruns.provider— ended chats survive a repo switching agents.Resolution semantics — the skip-layer rule
resolveSpawnOptionalready does and mirrors ADR-0021'sFilterSpawnOptionsrule for the cross-provider global options bag."", omits the CLI flag, and skips validation. Fixes both the claude-shaped-global-default AFK 400 trap and the empty-efforts-can-never-spawn bug.One select component
CatalogSelect's inherit-entry + "(not in catalog)" semantics as props and the repo picker's disabled/status rows (cloning %, clone failed).CatalogSelectis deleted, call sites migrate — no legacy shim.visualViewport(on-screen-keyboard-aware): open below by default, flip above only when below lacks room and above has more, clampmax-heightto the free space so the whole panel (with internal scroll) always fits.Also fix along the way
modelPick/effortPickare not catalog-validated when the effective provider changes (repo switch today, provider chip tomorrow) — a foreign pick would submit and 400. Reset picks when the effective provider changes.providers[0]; render base defaults against the global default provider and AFK defaults against the global AFK-effective provider.ADR
The implementing agent writes the ADR as part of this issue: three-level provider layering with AFK symmetry, skip-layer resolution, static catalogs, one select module. Amends ADR-0021 (spawn defaults/layering); touches ADR-0025 (composer surface) and ADR-0026 (seam v2 — unchanged, referenced).
Out of scope
Land-PR validation of #67 — verdict: PASS
Checked:
ci / nativegreen on head096bff6(4m51s) — SPA eslint/prettier/vitest/vite-build, ui-tagged Go build+test, golangci-lint.ci-nixcorrectly did not trigger (no nix/go.mod/go.sum changes), so the native gate fully covers this diff. Relied on; nothing re-run.maintip (merge base == main) — no conflict.Closes #66(AFK contract holds).repos.provider+afk_provider_defaultwith stamped rows reset to NULL (migration 0006, both dialects);ResolveProvider/Registry.DefaultFor/ reworkedlayerSpawnDefaultimplement strict-explicit + skip-layer-defaults + catalog[0] fallback + empty-catalog →""; settings seeding moved after provider registration (verified: before AFK loops and the HTTP server start); AFK scheduler/launch resolve through the one shared rule; composer agent chip gated at ≥2 providers, ephemeral pick, model/effort picks reset on effective-provider change, effort chip hidden on empty catalog; settings pages re-catalog against drafted providers;CatalogSelectdeleted with semantics preserved in the newSelect(incl. "(not in catalog)"); ADR-0030 + CONTEXT.md shipped. New tests pin the full chain matrix and the two-provider AFK scenario.One cosmetic nit, non-blocking: with zero ready repos the repo chip now reads "(unset)" where the old chip said "Choose repo" (send is disabled in that state anyway).