Land the e2e harness, brand, and capability-aware editors on main (0.5.0 prep)#32
Merged
Conversation
Every test so far asserted on the launch PLAN — the in-memory object planLaunch
and buildEnvPlan produce. Nothing ran the real bin/swisscode.js and observed
what it launched, so the execve handoff — the single most consequential line in
the tool, which ci.yml itself admitted nothing covered — was untested. This adds
a two-tier end-to-end harness that closes it, with no production code change: it
rides the SWISSCODE_*_BIN override seam that already exists.
TIER A — hermetic (test/e2e/*.e2e.ts, `npm run test:e2e`, every PR). Point the
three overrides at a recorder that writes down its own argv/env/cwd and exits.
Run the real binary; read the capture. The whole pipeline runs — parse, load,
resolve, lower, execve — with no network, no credential, full determinism. 29
cases: every provider through Claude Code, the three agents, the --cc-* surface,
passthrough, session mode, and the error/exit-code paths.
The assertion a plan test cannot make: for a third-party provider, the stale
ANTHROPIC_API_KEY from the polluted ambient env is ABSENT FROM THE LAUNCHED
CHILD. Present-in-plan.unset is not the same as gone-from-the-process; only a
real launch shows it. golden's claim, verified one layer further out — and it
now also covers the execve dispatch of replace(), which taking that branch in a
unit test could never do (it would replace the test runner; here it replaces the
child).
Two things the build itself taught, fixed rather than worked around:
- The recorder must be COPIED into /tmp, not symlinked from the repo: the
recursion guard realpaths every candidate and correctly rejects anything
under swisscode's own directory, which a symlink resolves back into. The
recursion-guard e2e depends on this being real.
- The Dockerfile needs `npm ci --ignore-scripts`: the repo's `prepare` is
`node build.js`, and at the deps layer the source it needs is not copied
yet. Caught by actually building the image, not by reading.
TIER B — real CLIs (test/e2e/*.real.ts, `npm run test:e2e:real`, manual only). A
Docker image installs @anthropic-ai/claude-code, @kilocode/cli and opencode-ai
at pinned versions and runs swisscode against them. Non-billable: it forwards
only --version, which every CLI answers before auth. It cannot be deterministic
(it depends on upstream), so e2e-real.yml is workflow_dispatch only and never
gates a PR — it catches an upstream flag rename when someone runs it. Verified
for real: the image builds and all three CLIs resolve and hand off cleanly.
The three test sets are disjoint by extension — *.test.ts (unit), *.e2e.ts
(hermetic), *.real.ts (Docker) — so `npm test` stays hermetic and fast and a PR
that has not installed the real CLIs stays green. Nothing ships: `files` is
bin/dist/README, so the packed size is unchanged.
783 unit tests, 29 hermetic e2e, 4 real (in Docker). ci.yml runs Tier A on every
PR across Node 22 and 24.
Signed-off-by: jellologic <31935831+jellologic@users.noreply.github.com>
(cherry picked from commit 240da80)
Brings the identity into the web UI and refreshes the shared marks, matched
across the config UI (sidebar logo, favicon, empty states) and the public
assets (README hero, npm social card).
THE MARK IS A TERMINAL PROMPT, NOT A SWISS CROSS. A white cross on red is the
Swiss flag and coat of arms — restricted for commercial use, and evocative of
the Victorinox "Swiss Army Knife" marks the product's name plays on. So the mark
keeps the red HANDLE (a colour is not protectable) and drops the cross for a
chevron-plus-cursor `❯█`. It reads three ways at once: a terminal prompt (this
is a coding tool), two blades opening from a pivot (the multi-tool homage the
name is actually about — many providers from one launcher), and none of the
protected emblems. It even rhymes with the `$ █` cursor in the hero's terminal
card.
Scoped by two findings: the trademarked phrase "Swiss Army Knife" appears
nowhere in the repo and "swiss" only ever inside "swisscode", so the name, the
two-tone swiss·code wordmark, and the brand red all stay — only the emblem
changes. And a modified cross does not help, since white-plus-on-red reads as
the flag regardless; the cross had to go entirely.
- web/src/Brand.tsx — Logo (chevron + block cursor on the red tile), Wordmark
(swiss in text, code in coral), BrandMark for the sidebar, and a branded
EmptyState/EmptyGraphic (a faint prompt in a dashed card) for the four true
empty states. The header comment records WHY the cross was retired so it is
not "restored" later.
- web/public/favicon.svg — the same mark, served same-origin so the CSP stays
'self'. index.html gains the icon link and theme-color metas.
- assets/hero.svg + social.svg — cross replaced by the same mark via a
transform reusing the exact geometry; hero.png (2400×760) and social.png
(1280×640) regenerated with rsvg-convert to match.
- Two brand.* tokens carry it, RESERVED for identity and never the interactive
accent (a brand-red button would read as danger, since the dark-mode danger
and the coral are nearly the same red).
Verified in a browser, both themes: the mark and empty-state graphic render, the
wordmark measures 4.57:1 light / 6.14:1 dark (both clear AA for text), the
favicon reads at 16px, the regenerated PNGs show the new mark at full size, and
no cross geometry remains in any of the four surfaces. The one hardcoded colour
in the new code is the white of the prompt — intrinsic to the mark, not a theme
value, stated where strictTokens does not reach.
783 tests, 139.9 kB packed (ceiling 150).
Signed-off-by: jellologic <31935831+jellologic@users.noreply.github.com>
(cherry picked from commit 74e9b48)
The agent-profile editor offered inputs the selected agent silently ignores,
and its model picker was non-functional exactly where it is named. Both come
from the same root: the editor did not consult what it already holds — the
selected agent's capabilities, and a provider to browse. This fixes that, and
the same class of mismatch on the neighbouring screens.
MODEL SELECTION STAYS ON THE AGENT PROFILE — the v3 split is correct. Model ids
are bare, provider-agnostic strings so one setup can back an OpenRouter account
AND a z.ai one without redefinition; moving the pins to Profiles would re-couple
model choice to the credential and still not resolve which catalog a multi-
account profile browses. What was actually wrong: browsing needs a provider and
the setup names none, so the editor BORROWED one from a downstream profile —
null on the "New agent profile" screen, where the Browse buttons then vanished.
Replaced with an explicit "Browse models against" provider lens: any provider,
defaulting to the borrowed/default one, used only for the Browse buttons and the
default-model placeholders, never stored. No schema change.
CAPABILITY-AWARE EDITORS (AgentCapabilities was modelled but unused by the UI):
- Model slots follow capabilities.models: Kilo ('single') shows one slot
labelled "model", OpenCode ('primary+small') shows primary + small, Claude
Code ('tiers') shows the four. Mapped onto the tier keys the adapters read
(opus; opus+haiku; all four), so the four-tier storage is unchanged and a
hidden slot keeps its value. The listing counts only the slots the agent uses.
- Gateway-compat section shows only for agents with capabilities.compatFlags
(Claude Code). For Kilo/OpenCode it is hidden; flags a previous Claude Code
setup left behind are kept and explained, not dropped.
- The Models copy is agent-aware — the [1m] extended-context sentence appears
only for the agent that sends it.
- Skip-permissions no longer hardcodes --dangerously-skip-permissions; the
concrete flag shows only for Claude Code (OpenCode lowers to --auto, Kilo to
a config block).
PROVIDER-AWARE where the provider is known:
- Accounts offers session mode only for the first-party Anthropic endpoint
(baseUrl null, not the custom "ask" provider) — a ~/.claude login the other
providers cannot read. Switching to a gateway clears a stale configDir so the
subscription token can never be shipped to a foreign host.
- Profiles no longer badges a session account "no key" — it reuses core's
credentialSource(), so a login counts as a credential and only a truly
empty account warns.
THE ONE MISMATCH WITH NO NET, closed: a compat flag on a Kilo/OpenCode profile
was inert at edit, launch AND doctor. `compatIgnoredWarning` (shared.ts) now
fires from both non-Claude adapters, so the doctor's agentWarnings pass surfaces
a hand-edited or agent-switched config the UI's hiding cannot catch — the
capability-gap-never-silently-dropped rule the tier and session warnings follow.
Verified in a browser against a config with all three agents and a session
account: each agent shows its own slot count, compat hides for the two that
ignore it, session mode is offered only for Anthropic, and the session account
is no longer mislabelled. 785 tests, 140.6 kB packed.
Signed-off-by: jellologic <31935831+jellologic@users.noreply.github.com>
(cherry picked from commit 2d67ca8)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why this PR exists. #28–#31 all merged, but a stacked-squash race meant only #28's content reached
main: #28 merged tomainat 12:00:13, before #29/#30/#31 merged into their branch bases at :16/:23/:32. So the e2e harness, the brand, and the capability-aware editors landed onfeat/web-ui-refresh/feat/web-brandinginstead ofmain. Squash-merging also rewrote SHAs, so those branches can no longer be merged intomainwithout re-applying content it already has.This recovers it cleanly:
mainalready holds the UI base content (verified byte-identical to35d545f), so the three feature commits are cherry-picked on top — no divergence, no conflicts, linear history.Contents, unchanged from their reviewed-and-green PRs:
b5cee35— the two-tier e2e harness (Run the real binary, and the real CLIs: a two-tier e2e harness #29)a5cff98— the code-forward brand mark (swisscode brand: a code-forward mark, logo, and wordmark #30)e34d7b8— capability-aware config editors (Make the config editors consult the agent and a provider #31)Verified the integration, not just the parts: the resulting
web/andsrc/trees are byte-identical tofeat/ux-capability-gating, andtest/e2e/+ CI + docs identical tofeat/e2e-harness— so this is exactly the code that was reviewed, recombined. 785 unit tests, 29 hermetic e2e, 140.7 kB packed (ceiling 150). All commits signed off.Merging this puts
mainat the state 0.5.0 will be tagged from.