Skip to content

Adopt the SolidJS front-end as the default gateway UI (4.5.1) (4.5.2) (4.5.3) (4.5.4) (4.5.5) (4.5.6) - #275

Open
leynos wants to merge 37 commits into
mainfrom
adopt-solidjs-ui
Open

Adopt the SolidJS front-end as the default gateway UI (4.5.1) (4.5.2) (4.5.3) (4.5.4) (4.5.5) (4.5.6)#275
leynos wants to merge 37 commits into
mainfrom
adopt-solidjs-ui

Conversation

@leynos

@leynos leynos commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

This branch replaces the legacy handwritten browser shell with the SolidJS
single-page application from axinite-mockup as the default gateway UI
(RFC 0018 Stages 1–4), implements the RFC 0009 feature-flag delivery
mechanism end to end, restores the legacy shell's operator surfaces in the
new UI, and migrates the Python end-to-end suite to the SolidJS DOM. The
SPA is authored in web-src/, built to stable artefact names, and embedded
into the binary exactly as the legacy assets were, preserving the
one-binary, local-first deployment model; a Bun mock backend provides a
documented daemon-free stub runtime (make frontend-stub). The legacy
shell remains embedded solely as a rollback path behind
AXINITE_WEB_UI=legacy (its removal is RFC 0018 Stage 5).

Roadmap tasks: (4.5.1) (4.5.2) (4.5.3) (4.5.4) (4.5.5) (4.5.6); the
deferred flag-change SSE event is recorded as new task 4.5.7.

Execplans (both implemented by this branch):
docs/execplans/adopt-solidjs-ui.md
and
docs/execplans/adopt-solidjs-ui-followups.md.
RFC 0009 is marked Implemented (with noted deviations) in
docs/rfcs/0009-feature-flags-frontend.md.

Review walkthrough

Validation

  • make check-fmt, make lint (three clippy feature combinations plus
    whitaker): pass, zero warnings.
  • cargo nextest run --workspace --features test-helpers: 4272 passed,
    8 skipped; github-tool tests 5/5.
  • make markdownlint, make nixie, spelling gates: pass.
  • make frontend-full (Tailwind compile, Biome, TypeScript, vitest unit
    and accessibility suites, Fluent checks, semantic-CSS rules, workspace
    Playwright spec, moz-fluent-lint): pass.
  • make frontend-verify (embedded-asset staleness): pass.
  • pytest tests/e2e/ -v against the real daemon (libsql build): 35
    passed, 1 skipped (live-registry skills install self-skip).
  • Browser validation via Playwright (stub runtime): initial load from
    fixtures, SSE-driven chat turn, flag toggles hiding navigation entries,
    failure fixtures rendering error states, zero console errors; css-view
    layout checks across routes at 1280/768/375 px show no overflow.
  • CodeRabbit CLI on the branch diff: zero findings (two runs, no rate
    limiting).

Notes

  • Deliberate deviations are catalogued in the RFC 0009 implementation
    notes: the dedicated override table (instead of extending settings),
    the optional X-Deployment-Id on reads (defaulting to "default"),
    400 responses for feature_flag: keys via the settings read/delete
    paths, and the disable-only subsystem layer.
  • The Python e2e mock LLM previously served only /v1/-prefixed paths
    while the daemon posts to {base}/chat/completions; every mock LLM turn
    had been silently 404ing. Fixed in
    tests/e2e/mock_llm.py.
  • Remaining follow-up recorded on the roadmap and in the execplan
    retrospectives: RFC 0018 Stage 5 (remove the legacy shell and
    tests/web_static_app.test.mjs once the rollback window closes) and
    roadmap task 4.5.7 (the feature_flags_changed SSE event).

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @leynos, your pull request is larger than the review limit of 150000 diff characters

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Summary

  • Made the SolidJS SPA the default embedded gateway UI, with AXINITE_WEB_UI=legacy rollback support.
  • Added Bun-based daemon-free preview/mock runtime with deterministic HTTP/SSE fixtures.
  • Implemented RFC 0018 Stages 1–4 and RFC 0009 feature flags, including deployment-scoped persistence, precedence rules, subsystem gating, settings interception, and version headers.
  • Restored operator surfaces including logs, chat media/auth cards, restart, TEE attestation, pairing, and detailed job views.
  • Migrated Python Playwright tests to the SolidJS DOM using a documented testability contract.
  • Added frontend build, asset embedding, verification, linting, accessibility, localisation, and CI workflows.
  • Added and updated execution plans documenting the SolidJS adoption and follow-up work:
    • docs/execplans/adopt-solidjs-ui.md
    • docs/execplans/adopt-solidjs-ui-followups.md

Validation

Frontend, Rust, end-to-end, formatting, linting, documentation, embedded-asset, and CI workflow checks are reported as passing.

Remaining work

  • RFC 0018 Stage 5: remove the legacy shell.
  • Roadmap task 4.5.7: add the feature_flags_changed SSE event.

Walkthrough

This pull request adopts a SolidJS single-page application as the default Axinite browser UI, replacing the legacy handwritten shell. It implements RFC 0009 deployment-scoped feature-flag persistence and a GET /api/features endpoint, adds a Bun mock backend for daemon-free preview, rewrites the Python Playwright e2e suite against the new SolidJS DOM, and updates supporting CI, tooling, and documentation.

Changes

RFC 0009 Feature Flags Backend

Layer / File(s) Summary
Migrations and schema
migrations/V18__feature_flag_overrides.sql, migrations/libsql_schema.sql, src/db/libsql_migrations.rs
Adds a feature_flag_overrides table keyed on (deployment_id, flag_name) via a new idempotent migration and matching libSQL incremental entry.
Registry and /api/features handler
src/channels/web/handlers/feature_registry.rs, src/channels/web/handlers/features.rs, src/channels/web/handlers/mod.rs
Adds an in-memory FeatureFlagRegistry, deployment-id header parsing, and GET /api/features with env-var → override → subsystem-availability → compiled-default resolution.
Settings handler interception
src/channels/web/handlers/settings.rs, src/channels/web/handlers/settings/tests.rs
Rejects feature_flag: keys on GET/DELETE /api/settings and routes PUT to a new set_feature_flag flow validating deployment header, flag name, and boolean coercion.
SettingsStore trait/backends
src/db/traits/settings.rs, src/db/forwarders.rs, src/db/libsql/settings.rs, src/db/postgres/settings.rs, src/history/store/settings.rs, and numerous test doubles
Extends list_deployment_flags/set_deployment_flag across all SettingsStore implementations, native backends, and stub/mock stores.
Gateway state wiring
src/channels/web/server.rs, src/channels/web/mod.rs, src/channels/web/test_helpers.rs, various test fixtures
Adds a shared feature_flags field to GatewayState, wires features::routes(), and preserves the registry across state rebuilds.
Documentation
docs/rfcs/0009-feature-flags-frontend.md, docs/roadmap.md, docs/execplans/*, docs/solidjs-frontend.md, docs/front-end-architecture.md
Marks RFC 0009 implemented with deviation notes and documents the SolidJS migration and follow-up execplans.

Embedded SPA and Legacy UI Serving

Layer / File(s) Summary
UiVariant routing
src/channels/web/handlers/ui_assets.rs, src/channels/web/handlers/static_files.rs
Replaces the old public router with UiVariant::Solid/Legacy selection via AXINITE_WEB_UI, serving embedded SPA shell/assets/locales or the legacy shell.
Embedded build output and locales
src/channels/web/static/solid/...
Adds the built app.js, index.css, index.html, and Fluent locale bundles for ten languages, embedded for the default UI.

SolidJS Frontend Application

Layer / File(s) Summary
Bootstrap, router, shell, auth gate
web-src/axinite/src/main.tsx, app/*, components/app-shell.tsx, components/auth-gate.tsx, components/route-page.tsx, lib/test-hooks.ts
Boots the app, installs window.__axinite test hooks, resolves auth via /api/gateway/status, and renders the route tree with feature-flag gating.
API client and contracts
web-src/axinite/src/lib/api/*
Adds typed fetch/SSE wrappers and full contract types for chat, gateway, jobs, logs, memory, pairing, routines, extensions, and skills.
Supporting libraries
web-src/axinite/src/lib/{auth,base-path,connection-status,restart,tee,markdown,string-case}.ts
Adds token storage, base-path helpers, connection-state signal, the restart state machine, TEE attestation client, and markdown/string helpers.
i18n and feature flags
web-src/axinite/src/lib/{i18n,feature-flags}/*
Adds the runtime feature-flag registry/provider and Fluent/i18next-backed locale provider with RTL/detection support.
Domain preview components
web-src/axinite/src/components/*
Adds chat, memory, jobs, routines, extensions, skills, logs, restart, TEE, and WASM-stepper preview components with data fetching, mutations, and SSE wiring.
Semantic styles
web-src/axinite/src/styles/*.css
Adds the shell/component/dialog/responsive semantic stylesheet.

SolidJS Frontend Tests and Locales

Layer / File(s) Summary
Public locale bundles
web-src/axinite/public/locales/*/common.ftl
Mirrors the embedded gateway Fluent bundles for the standalone dev preview.
Test suite
web-src/axinite/tests/**
Adds Vitest unit/behaviour/a11y tests and a Playwright smoke test covering the shell, chat, extensions/pairing, jobs, logs, restart, TEE, feature flags, and API contracts.

Mock Backend

Layer / File(s) Summary
Fixtures and state
web-src/mock-backend/src/{fixtures,state,state-helpers}.ts
Adds deterministic seed data and MockBackendState implementing chat/SSE, memory, jobs, routines, extensions, skills, and pairing behaviour.
Server and preview gateway
web-src/mock-backend/src/{server,preview-server,streaming-routes}.ts
Adds the declarative HTTP/SSE route table and a same-origin Bun preview server proxying /api/*.

Python E2E Test Migration

Layer / File(s) Summary
Shared helpers
tests/e2e/{helpers.py,conftest.py,mock_llm.py,CLAUDE.md,.gitignore}
Replaces tab-based selectors with SolidJS SEL/ROUTES/goto_route helpers and updates fixture environment/navigation waits.
Scenario rewrites
tests/e2e/scenarios/*.py
Rewrites chat, connection, XSS, SSE-reconnect, extensions, skills, and tool-approval scenarios to drive the SolidJS DOM via window.__axinite hooks and role/testid selectors.

Tooling, CI, and Documentation

Layer / File(s) Summary
CI and Makefiles
.github/workflows/frontend.yml, Makefile, web-src/Makefile
Adds a frontend verification workflow and build/verify/check/test/stub make targets.
Configuration
typos.toml, .codescene/*, web-src/{biome.jsonc,package.json,tsconfig.json,vite.config.ts,playwright.config.ts,vitest*.config.ts}
Excludes generated frontend assets from spelling/code-health checks and configures the new workspace's build/lint/type/test tooling.
Guides and execplans
web-src/docs/*.md
Adds architecture, Tailwind/DaisyUI, accessibility-testing, and migration guide documentation.

Sequence Diagram(s)

sequenceDiagram
  participant SPA as SolidJS App
  participant Gateway as Axinite Gateway
  participant Registry as FeatureFlagRegistry
  participant Store as SettingsStore

  SPA->>Gateway: PUT /api/settings/feature_flag:route_memory (X-Deployment-Id)
  Gateway->>Gateway: validate flag name and coerce value
  Gateway->>Registry: hydrate(deployment_id) if needed
  Gateway->>Store: set_deployment_flag(deployment_id, flag, enabled)
  Gateway->>Registry: set(deployment_id, flag, enabled)
  Gateway-->>SPA: 200 SettingResponse

  SPA->>Gateway: GET /api/features (X-Deployment-Id)
  Gateway->>Registry: get(deployment_id, flag)
  Gateway-->>SPA: resolved flags + X-Axinite-Version
Loading
sequenceDiagram
  participant Test as Playwright Test
  participant Page as SolidJS Page
  participant Hooks as window.__axinite
  participant Backend as Mock/Real Backend

  Test->>Page: goto("/chat")
  Test->>Hooks: emitChatEvent({type: "job_started", ...})
  Hooks->>Page: update chat UI (job card)
  Test->>Hooks: closeChatStream()
  Hooks->>Page: sse-status data-state="disconnected"
  Test->>Hooks: reconnectChatStream()
  Hooks->>Backend: reopen SSE connection
  Backend-->>Page: onopen
  Page->>Test: sse-status data-state="connected"
Loading

Possibly related PRs

  • leynos/whitaker#277: Both PRs touch Typos-based spelling-exclusion configuration for newly added frontend assets and locales.

Suggested labels

Roadmap

Poem

Hop, hop, SolidJS springs to life,
Flags now flip without the strife,
A mock backend hums in Bun's embrace,
Playwright rabbits find the new chase,
🐇✨ Ten locales sing in chorus bright —
Legacy shell, sleep well tonight.


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (4 errors, 8 warnings)

Check name Status Explanation Resolution
Testing (Overall) ❌ Error test_skills_install_and_remove can pass if the Remove button disappears, so a key regression would slip through. Make absence of the Remove control fail or skip explicitly, and wait/assert on removal so the test guards the delete flow.
Module-Level Documentation ❌ Error Several new TS/TSX modules start with imports rather than module docstrings, e.g. chat-preview.tsx, app-shell.tsx, client.ts, and runtime.tsx. Add a top-of-file module docstring to every changed module, especially the TS/TSX files, and explain each file’s purpose and relation to the SPA stack.
Unit Architecture ❌ Error GET /api/features hydrates and mutates shared cache on a read path, logs APIs swallow failures, and both the gateway UI variant and mock backend read ambient env directly. Make feature hydration additive and side-effect-free on GET, surface log-level fetch/write errors, and inject env/config at boundaries instead of reading process state inline.
Security And Privacy ❌ Error FAIL: caller-controlled X-Deployment-Id is only trimmed, and several client APIs interpolate unencoded path segments, enabling unbounded keys and route injection. Validate and bound deployment IDs, and encode every dynamic path segment in the TS API wrappers (or use safe route builders) before merging.
Description check ⚠️ Warning The description covers summary and validation, but it omits most required template sections such as Change Type, Linked Issue, Security Impact, and Rollback Plan. Add the missing template sections: Change Type, Linked Issue, Security Impact, Database Impact, Blast Radius, Rollback Plan, and Review track.
Docstring Coverage ⚠️ Warning Docstring coverage is 13.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
User-Facing Documentation ⚠️ Warning docs/users-guide.md never mentions the new SolidJS UI, feature flags, stub runtime, or route surfaces; it only covers skills, workers, markdown, self-repair, and memory. Add a users-guide section for the browser UI, frontend stub, feature flags, and new route/operator surfaces; keep any translated user guides aligned with the lead locale.
Developer Documentation ⚠️ Warning Roadmap 4.5.x is still unchecked, the execplan keeps delivered work in remaining follow-ups, and zh-CN/ar/ja still ship English strings. Update docs/developers-guide.md and the relevant design docs, check off completed roadmap items, trim the execplan to only open work, and finish the untranslated locale keys.
Testing (Property / Proof) ⚠️ Warning PR adds range/state invariants (feature-flag precedence, restart/stepper transitions, mock-backend event ordering) but only example-based tests; no proptest/Kani/Verus coverage in touched areas. Add bounded property/model tests for precedence, hydration, transition-ordering, and stepper invariants—or document why example cases are exhaustive.
Observability ⚠️ Warning FAIL: dev.ts logs crashed child exits but always exits 0, and handleMockRequest turns handler exceptions into 400s without server-side logging. Return a non-zero exit code on unexpected child exits, and log mock-backend handler exceptions before converting them to responses.
Performance And Resource Use ⚠️ Warning Deployment IDs are accepted verbatim and cached/persisted per key, so callers can grow the registry and DB keys without bound; /api/features also re-reads env vars on every request. Validate and bound X-Deployment-Id (charset/length), make hydration additive-only, and cache the env overlay once per process before resolving flags.
Concurrency And State ⚠️ Warning FAIL: ensure_deployment_hydrated() checks then awaits then overwrites cache; tests only cover single-threaded precedence, not concurrent first-read/write races. Make hydration non-destructive or singleflight per deployment, and add a concurrent GET /api/features vs settings-write race test.
✅ Passed checks (8 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the PR's main change and includes the roadmap item references.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Testing (Unit And Behavioural) ✅ Passed PASS: Unit tests cover edge/error paths (feature flags, auth, logs, tee, restart); behaviour tests render public components and drive DOM/API boundaries; Playwright e2e rewrites cover real user flows.
Testing (Compile-Time / Ui) ✅ Passed PASS: Rust compile-contract coverage is via trybuild (tests/trybuild.rs + settings_compat.rs), and TS compile-time coverage is the tsc --noEmit typecheck gate.
Domain Architecture ✅ Passed Feature-flag policy stays behind handler/repository boundaries; the core registry is pure, and HTTP/env/SQL concerns are confined to adapters.
Architectural Complexity And Maintainability ✅ Passed PASS: Each new layer (UI variant routing, feature-flag cache, typed API contracts, mock backend state) serves an immediate seam and is documented/tests-backed; no speculative abstraction stands out.
Rust Compiler Lint Integrity ✅ Passed No new Rust lint suppressions or artificial-use anchors appear in the touched files; the only clone is a test snapshot (writes()), and new helpers stay behind #[cfg(test)].
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch adopt-solidjs-ui

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added size: XL 500+ changed lines scope: channel/web Web gateway channel scope: db/postgres PostgreSQL backend scope: db/libsql libSQL / Turso backend scope: ci CI/CD workflows scope: docs Documentation scope: channel/wasm WASM channel runtime risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs and removed size: XL 500+ changed lines labels Jul 19, 2026
leynos added 18 commits July 19, 2026 20:53
Record the migration plan implementing RFC 0018 Stages 1-3: import the
`axinite-mockup` SolidJS workspace as `web-src/`, fix the contract
breaks catalogued in `docs/solidjs-pwa-gap-analysis.md`, embed the
built SPA as the default gateway UI with an `AXINITE_WEB_UI=legacy`
rollback switch, add a minimal env-var-driven `GET /api/features`, and
keep the Bun mock backend as the daemon-free stub runtime.
Vendor the SolidJS browser workspace from the `axinite-mockup`
repository (RFC 0018 Stage 1) unchanged, excluding VCS metadata and
build output. The workspace carries its own Bun/Vite/Biome/Vitest/
Playwright toolchain, typed API modules, feature-flag registry, Fluent
localisation bundles, and the Bun mock backend that will serve as the
daemon-free stub runtime.

Verified in isolation: `bun install`, `bun run check:types`,
`bun run lint`, `bun run check:fmt`, `bun run test` (19 passing),
`bun run test:a11y` (2 passing), and `bun run build` all succeed.

Adaptation to the gateway serving model (base path, stable asset
names, contract fixes) follows in subsequent commits per
`docs/execplans/adopt-solidjs-ui.md`.
Serve the SolidJS app from the site root instead of the
`/axinite-mockup/` GitHub Pages prefix:

- Rename `GITHUB_PAGES_BASE_PATH` to `DEPLOY_BASE_PATH` (`"/"`), and
  update the base-path and e2e tests for root-relative routes.
- Emit stable, hash-free build artefacts (`assets/app.js`,
  `assets/index.css`, `assets/axinite32.ico`) so the gateway can embed
  a fixed file list with `include_str!`/`include_bytes!`.
- Drop the GitHub Pages machinery: the `postbuild-routes.mjs` route
  mirroring, `test-build.mjs`, and the `vite-plugin-pwa` service
  worker, which would fight the gateway's `no-cache` serving model.
- Remove the hand-authored HTML design mockups
  (`axinite/{chat,memory,jobs,routines,extensions,skills}/` and their
  vendored CDN assets); the Vite build never consumed them.
- Give the preview server a single-page-app fallback so extension-less
  routes resolve to the app shell, matching gateway behaviour.
- Point mock job fixtures' `project_dir`/`browse_url` at root paths.
- Update the stale e2e chat and memory assertions to match the current
  component markup (no level-2 headings on those routes).

Verified: typecheck, biome lint/format, 20 unit tests, 2 a11y tests,
Playwright e2e, and `bun run build` all pass.
Fix the contract breaks catalogued in
`docs/solidjs-pwa-gap-analysis.md` so the typed client and mock
backend speak the daemon's dialect:

- `LogEntry` now carries `target` (as emitted by
  `src/channels/web/log_layer.rs`) instead of the invented `source`,
  and drops the synthetic `id` the gateway never sends (gap G3).
- `JobPromptRequest` becomes `{ content, done? }`, matching
  `POST /api/jobs/{id}/prompt` (gap G4).
- `installExtension` accepts the full `InstallExtensionRequest`
  (`name`, `url?`, `kind?`) rather than narrowing to `{ name }`
  (gap §11.1).

Add `axinite/tests/api-contract-alignment.test.ts` pinning these
shapes at both the client boundary (request bodies) and the mock
backend (log subscription payloads, job prompt handling); the tests
failed against the previous shapes and pass now.
The real gateway protects every `/api/*` route with a bearer token and
accepts a `?token=` query parameter on its SSE endpoints because
`EventSource` cannot set headers (gap G1 in the SolidJS PWA gap
analysis). The SPA previously sent unauthenticated requests only.

- Add `@/lib/auth/token`: sessionStorage-backed token storage plus
  `appendTokenToUrl` for the streaming endpoints.
- Inject `Authorization: Bearer` into every typed-client request and
  thread the token into `createEventStream` URLs.
- Add an `AuthGate` boot component that probes
  `GET /api/gateway/status` once: anonymous 200 (the mock backend)
  opens the gate immediately; 401 presents a localized token form,
  verifies the token against the same endpoint, and stores it on
  success. Localized copy is provided for all ten locales.

Covered by `auth-token.test.ts` (storage, header injection, SSE URL
propagation) and `auth-gate.behaviour.test.tsx` (anonymous access,
unlock on valid token, rejection message). 34 unit tests, typecheck,
lint, Fluent coverage, and the Playwright e2e all pass.
Embed the built SolidJS artefacts (`src/channels/web/static/solid/`,
produced by `make frontend-build` from `web-src/`) and serve them as
the default browser UI:

- `routes_for(UiVariant)` builds the public asset router. The Solid
  variant serves the app shell at `/` and at each client-side route
  (`/chat`, `/memory`, `/jobs`, `/routines`, `/extensions`, `/skills`)
  so deep links and reloads work, plus the stable-named bundle
  (`/assets/app.js`, `/assets/index.css`), the icon, and the ten
  embedded Fluent locale bundles at `/locales/{locale}/common.ftl`.
- The legacy handwritten shell stays embedded solely as a rollback
  path (RFC 0018 Stage 3), selected with `AXINITE_WEB_UI=legacy`.
- New Make targets: `frontend-install`, `frontend-build`,
  `frontend-verify` (staleness gate for the embedded copy),
  `frontend-check`, `frontend-test`, and `frontend-stub` (daemon-free
  Bun mock API + preview server).

Committing the built assets keeps `cargo build`, Docker, and cargo
packaging hermetic — no Bun toolchain is needed for Rust-only builds.

Unit tests cover SPA shell serving on every app route, stable asset
names and content types, locale bundle lookup (including unknown
locale 404), legacy-variant serving, and the default variant
resolution.
Implement the minimal slice of RFC 0009 the SolidJS app already
consumes: an authenticated `GET /api/features` endpoint returning a
flag-name-to-boolean map. Each flag resolves from a
`FEATURE_FLAG_<UPPER_SNAKE_NAME>` environment variable (`true`,
case-insensitively, enables; any other set value disables) and falls
back to compiled defaults mirroring
`web-src/axinite/src/lib/feature-flags/registry.ts`.

The settings-table override layer and deployment scoping from RFC 0009
remain future work; the resolution order here (environment over
compiled default) matches the top of that RFC's precedence chain, so
the richer mechanism can slot in beneath it later.

Previously the endpoint did not exist and the SPA silently fell back
to client-side defaults (gap G2 in the SolidJS PWA gap analysis).
Make the Bun mock backend a faithful, deterministic stand-in for the
gateway surface the SolidJS app consumes:

- `GET /api/features` now returns the RFC 0009 flat name-to-boolean
  map (the shape the real gateway serves) with the full thirteen-flag
  registry, and honours `FEATURE_FLAG_<UPPER_SNAKE_NAME>` environment
  overrides using the same semantics as the gateway, so stub runs can
  exercise flag combinations.
- `MOCK_FAILURES` (comma-separated request paths) makes the listed
  routes return a deterministic HTTP 500 fixture for error-state
  validation without the daemon.

`axinite/tests/mock-backend-contract.test.ts` exercises the stub
in-process through `handleMockRequest`: feature-flag shape and
override behaviour, gateway status telemetry, every initial-load list
route, failure fixtures, unknown-route 404s, and both SSE routes —
`text/event-stream` headers, gateway-shaped log replay frames, and the
chat turn lifecycle ordering (`thinking` before `tool_started` before
`response`) with `event:` names matching each payload's `type`.
Playwright smoke testing against the daemon-free stub exposed three
issues in the imported SPA:

- Navigation entries ignored the `route_*` feature flags: the route
  pages honoured them (rendering the unavailable notice) but the shell
  nav always listed every route. Nav links now hide when their route
  flag resolves off, so overrides and server flags gate the whole
  surface consistently.
- List routes swallowed query failures: a failing `/api/jobs` left a
  silently empty table. The jobs route now renders a localized,
  `role="alert"` error notice when the list request fails (validated
  against the stub's `MOCK_FAILURES=/api/jobs` fixture).
- `connectChatEvents` registered an SSE listener for the `error` event
  type, which also receives the browser's built-in connection-failure
  Event; parsing its undefined `data` threw
  `SyntaxError: "undefined" is not valid JSON` on every dropped
  connection. Both SSE clients now ignore events without a string
  payload.

Each fix carries a regression test (nav gating in the app-shell
behaviour suite, a new jobs-preview behaviour suite, and an
EventSource dispatch test).
- Add `docs/solidjs-frontend.md`: commands, the daemon-free stub
  runtime, the stubbed HTTP and SSE routes, failure fixtures
  (`MOCK_FAILURES`), feature-flag overrides, gateway serving and auth,
  how the stub differs from the daemon, and the test layers.
- Mark `docs/front-end-architecture.md` as the legacy-fallback
  reference with a transitional banner pointing at the new document
  (RFC 0018 documentation-migration requirement).
- Update `src/channels/web/CLAUDE.md` with the variant-dependent
  static routes and `GET /api/features`.
- Link the new document from the README.
- Pin `AXINITE_WEB_UI=legacy` in `tests/e2e/conftest.py`: those
  scenarios drive the legacy DOM (tab bar, approval overlay,
  `?token=` boot) and their SolidJS rewrite is tracked follow-up work
  in `docs/execplans/adopt-solidjs-ui.md`. `tests/web_static_app.test.mjs`
  continues to pass against the retained legacy assets.
- Record browser/css-view validation results and decisions in the
  ExecPlan.
- Split `handlers/static_files.rs` (512 lines, over the whitaker
  400-line module cap) by moving UI-variant selection and all embedded
  asset serving into a new `handlers/ui_assets.rs`; `static_files.rs`
  keeps the logs, gateway-status, health, and project-file handlers.
- Adopt Oxford spellings in `web-src` identifiers and prose:
  `normalizeBasePath`, `capitalize`, `CatalogueSkillEntry`,
  `catalogueSkills`/`catalogueMatch`, and `Summarize` in the en-GB
  strings and fixtures. Wire tokens the daemon defines (`catalog`,
  `catalog_error`) keep their upstream spelling behind targeted typos
  ignore patterns.
- Extend `typos.local.toml`: exclude the generated SPA bundle and
  icons, translated Fluent bundles (en-GB stays checked), the CSS
  sources (US-English property syntax, matching the legacy
  `style.css` precedent), and the vendored mockup reference docs;
  regenerate `typos.toml`.
- Fix remaining en-GB spellings in the vendored architecture doc
  headings that are still maintained, and align two Markdown tables
  with the MD060 column style.
- Rebuild the embedded SPA assets so `make frontend-verify` matches
  `web-src` (picks up the auth-gate strings, `jobs-load-error`, and
  the renames).

`make check-fmt`, `make lint`, `make typecheck`, `make markdownlint`,
`make nixie`, web-channel nextest (148 tests), and the full web-src
suite (45 tests) all pass.
Mark the plan COMPLETE, record the final gate and CodeRabbit results,
and write the retrospective: delivered scope, the three defects that
browser validation caught beyond the suites, the remaining follow-up
work (Python e2e migration, RFC 0009 settings layer, UI parity gaps),
and lessons learned.
Plan the three follow-up streams: RFC 0009 deployment-scoped flag
persistence (dedicated `feature_flag_overrides` table, registry in
`GatewayState`, settings-handler interception), UI parity with the
legacy shell (logs route, restart, TEE, pairing, chat media and cards,
jobs detail fidelity, with the mock backend extended first), and the
Python e2e migration to the SolidJS DOM via a deliberate testability
contract (`?token=` boot, stable testids, `window.__axinite` hooks).
Restore the legacy shell's logs-tab parity (gap analysis §5.4, §6.4):
logs move from a transient dialog to a first-class `/logs` route,
gated by a new `route_logs` feature flag and listed last in the shell
navigation. The route streams over the existing SSE client with a
500-entry cap and adds the operator controls the dialog lacked: a
display-level filter, target substring filter, pause/resume, clear,
and an auto-scroll toggle, alongside the existing write-level select.
`panel_logs` now gates the stream surface inside the route, so
deployments that disabled the panel keep that behaviour.

`logs-dialog.tsx` is removed; strings are localized in all ten
locales; the mock backend advertises the new flag. Behaviour tests
cover streaming, both filters, pause/resume, clear, and level writes;
the shell behaviour/a11y suites and the workspace Playwright spec are
updated for the nav-link navigation. 51 unit tests, 2 a11y tests,
typecheck, biome, Fluent coverage, and the workspace e2e all pass.

The gateway-side counterparts (`route_logs` in `FLAG_DEFAULTS`,
`/logs` in `SOLID_APP_ROUTES`, embedded asset refresh) land with the
feature-flag persistence change.
Give the daemon-free mock backend the remaining surfaces the parity
work needs, mirroring the daemon DTOs in `src/channels/web/types/`:

- Pairing: `GET /api/pairing/{channel}` and `POST .../approve`, with a
  deterministic pending request (`PAIR-1234` on the `whatsapp`
  channel), empty lists for unknown channels, `success: false` for
  unknown codes, and a plain-text 429 fixture for the code
  `rate-limited`, matching the daemon's rate-limit response.
- Chat auth: `POST /api/chat/auth-token` (accepts `valid-token` or any
  token of eight-plus characters, publishing `auth_completed` on the
  chat stream) and `POST /api/chat/auth-cancel`.
- Deterministic `sendMessage` triggers: `/restart` emits a
  `restart`-named tool sequence and a "Restart initiated" response;
  image-flavoured prompts emit `image_generated` with an inline 1x1
  PNG data URL; job-flavoured prompts emit `job_started`; attached
  `images[]` are acknowledged in the response text.
- Fixtures: a `pairing`-status WASM channel (`whatsapp`) for the
  stepper branch and an OAuth-flavoured `google-drive` extension whose
  activation emits `auth_required` with `auth_url`/`setup_url`.
- `ChatSseEvent` gains the daemon's full `job_*` family and
  `image_generated`; pairing and chat-auth DTOs are added to
  `contracts.ts`.

Twelve new in-process contract tests pin these behaviours (63
workspace tests pass).
Add the operator-override layer beneath the environment-variable
feature-flag resolution:

- New `feature_flag_overrides` table, primary key
  `(deployment_id, flag_name)`, on both backends: Postgres refinery
  migration `V18__feature_flag_overrides.sql` and a libsql
  incremental migration (version 18; 17 deliberately skipped to stay
  aligned with Postgres, whose V17 is a no-op under libsql's dynamic
  typing).
- `SettingsStore`/`NativeSettingsStore` gain `list_deployment_flags`
  and `set_deployment_flag`, implemented for Postgres, libsql, and
  every test double; deployment flags never touch the user-scoped
  `settings` table.
- `FeatureFlagRegistry` (deployment -> flag -> enabled) lives in
  `GatewayState` behind `Arc<RwLock<..>>`, lazily hydrated from the
  store on first read per deployment; writes update the database and
  registry synchronously, so overrides are visible on the next
  `GET /api/features` without a restart.
- `PUT /api/settings/feature_flag:<name>` requires an
  `X-Deployment-Id` header (400 without), validates flag names
  (`[a-z0-9_]+`), and coerces JSON booleans or "true"/"false"
  strings; GET/DELETE of `feature_flag:` keys through the settings
  API return 400, directing callers to `GET /api/features`.
- `GET /api/features` resolves the deployment from an optional
  `X-Deployment-Id` header (default `"default"`); precedence is
  environment variable > deployment override > compiled default.
  `route_logs` joins the compiled defaults and the gateway serves the
  `/logs` app-shell route, matching the SPA's new logs route.

Registry, resolution, handler, and store round-trip tests cover the
precedence chain, header validation, deployment isolation, and
restart-free visibility (the handler test runs against a real
in-memory libsql store). The settings handler tests moved to
`settings/tests.rs` and the WASM wrapper's recording store to its own
module to respect the 400-line module cap.
@github-actions github-actions Bot added the size: XL 500+ changed lines label Jul 19, 2026
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

leynos added 2 commits July 20, 2026 16:28
The `NativeSettingsStore` compile-contract fixture predates the
deployment-flag methods added for RFC 0009, so CI's `ci` nextest
profile — the only profile that runs the trybuild binary; the default
profile excludes it, which is why local runs stayed green — failed on
`db_surface_compile_contracts::case_4`. Add no-op
`list_deployment_flags`/`set_deployment_flag` implementations to the
dummy store. All four compile-contract cases pass under the `ci`
profile locally.
The workspace `playwright.config.ts` defaults
`PLAYWRIGHT_BROWSERS_PATH` to `0` (hermetic, under `node_modules`) at
test time, but the workflow's install step downloaded Chromium to the
runner's default cache, so the workspace spec failed to find the
browser on the first CI run. Set the variable at job level so the
install and test steps agree on the location.
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

leynos added 2 commits July 20, 2026 17:23
Refactor every production finding from the CodeScene quality gate,
behaviour-preserving (126 unit, 2 a11y, semantic, Fluent, and
Playwright suites all green; DOM shape unchanged for the e2e
selector contract):

- Bumpy Road (critical): `handleMockRequest` becomes a declarative
  route table with a single dispatcher; `listMemory`,
  `resolveStaticPath`, `computeStepperModel` (now table-driven), and
  the Fluent-variable checker are flattened with extracted helpers.
- Complex/Large Method (advisory): the extensions, skills, routines,
  memory, and logs previews plus the jobs detail view are decomposed
  into focused same-file subcomponents with narrow props;
  `handleChatEvent` becomes a typed handler map; the TEE popover
  gains a report-loader helper; the restart and feature-flag
  conditionals are named predicates.
- Drop an unreachable `return` after `process.exit(1)` introduced by
  the checker refactor.

Extend `.codescene/code-health-rules.json` in line with the file's
existing policy: `web-src/axinite/tests/**` gets the same
test-shape leniency as `tests/**`, and the daemon-free mock harness
(`web-src/mock-backend/**`) down-weights Code Duplication and file
length with a documented rationale — its fixture volume and
contract-shaped response builders are inherent to its job, while its
structural findings were refactored rather than suppressed. No
`@codescene` suppression comments are used anywhere.

Refresh the embedded SPA assets to match the refactored sources.
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

CodeScene applies a single rule set per file, first match wins, so the
repo-wide `**` set was shadowing any later, more specific globs. Order
the sets most-specific-first and add three scoped sets:

- `src/channels/web/static/solid/**`: the minified SolidJS build
  output is generated (refreshed by `make frontend-build`), so its
  code-health findings are false positives against bundler output;
  the corresponding `web-src/` sources are analysed at full weight.
- `web-src/axinite/tests/**`: the same test-shape leniency the file
  already grants `tests/**`.
- `web-src/mock-backend/**`: the daemon-free stub harness down-weights
  duplication, file length, and primitive obsession with a documented
  rationale (wire-contract fixtures and stringly identifiers are
  inherent to its job); its structural findings were refactored at
  full weight, not suppressed.
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

leynos added 2 commits July 20, 2026 18:42
Second remediation round, verified per file with the CodeScene CLI:

- Mock backend: seed fixtures move to `fixtures.ts` (module-level data
  exposed through cloning builders, preserving per-instance isolation)
  and the chat-emission timeline, approval, and skill helpers move to
  `state-helpers.ts`, clearing the file-length finding and bringing
  `sendMessage`, `submitApproval`, and `installSkill` under the
  complexity thresholds with byte-identical event ordering.
- `resolveStaticPath` flattens to a pure candidate list plus a single
  find; the chat thread guard becomes a named predicate; the Fluent
  checker splits per-entry processing and gains a type-guard pair; the
  over-complex hook-surface test splits into three focused cases; and
  `appendTextCell` takes a typed spec object, clearing the
  extensions-preview primitive-obsession finding.
- Rust: `set_feature_flag` drops its redundant `key` parameter
  (reconstructed from the flag name), and the libsql settings and
  feature-flag writers share a timestamped-upsert helper, removing the
  introduced setter duplication; the file returns to its pre-branch
  baseline score.

128 workspace tests, a11y, semantic, Playwright, targeted Rust suites
(71), clippy, and fmt all pass; embedded assets rebuilt.
CodeScene applies a single rule set per file, so the mock-backend set
was shadowing the repo-wide String Heavy disable rather than adding to
it. Restate that disable in the harness set and disable Primitive
Obsession there outright with the documented rationale: the public
mock API deliberately takes the same string identifiers the wire
carries, and reshaping it would contort the contract mirror the stub
exists to provide. `cs delta origin/main HEAD` now reports zero
introduced or degraded findings across the branch.

@codescene-access codescene-access Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No quality gates enabled for this code.

@leynos
leynos marked this pull request as ready for review July 20, 2026 23:20
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @leynos, your pull request is larger than the review limit of 150000 diff characters

@pandalump

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot added the Roadmap label Jul 21, 2026
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 63

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/e2e/scenarios/test_extensions.py (1)

1-478: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Split this file along its existing Group A-H boundaries.

The file has grown to roughly 478 lines across 8 clearly-delineated groups (structural, installed cards, registry+install, configure panel, remove dialog, activate, stepper, pairing). Split into e.g. test_extensions_cards.py, test_extensions_configure.py, test_extensions_lifecycle.py — pytest auto-discovers any test_*.py under scenarios/, so no conftest changes are needed.

As per path instructions, "Files must not exceed 400 logical lines: Decompose large modules into subpackages."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/scenarios/test_extensions.py` around lines 1 - 478, Split
tests/extensions scenario file into multiple pytest-discoverable test_*.py
modules at the existing Group A-H boundaries, keeping related helpers and
fixture data with the groups that use them. Ensure each resulting module stays
under 400 logical lines, preserves all test behavior and imports, and requires
no conftest changes.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/frontend.yml:
- Around line 30-39: Update the workflow steps for Checkout repository, Install
Bun, and Install uv to reference immutable commit SHAs instead of version tags.
Add persist-credentials: false to the actions/checkout configuration so its
token is unavailable to later scripts, while preserving the existing Bun and uv
version settings.

In `@docs/execplans/adopt-solidjs-ui.md`:
- Around line 231-240: Update the “Remaining follow-up work” section in the
completed plan to remove the delivered SolidJS E2E migration and
deployment-scoped feature-flag items, or explicitly mark the section as
historical. Retain only genuinely outstanding UI parity work, including the
referenced gap-analysis items.

In `@docs/rfcs/0009-feature-flags-frontend.md`:
- Around line 497-518: Update docs/rfcs/0009-feature-flags-frontend.md:497-518
to explicitly supersede earlier requirements with the shipped dedicated override
table, optional deployment header for reads, required header for writes, and
disable-only subsystem defaults. Update docs/roadmap.md:1163-1172 so tasks
4.5.3–4.5.5 reflect the implemented contract, leaving task 4.5.7 as the
remaining SSE work.

In `@docs/roadmap.md`:
- Around line 1163-1172: Update roadmap tasks 4.5.3–4.5.5 to use the current
feature_flag_overrides storage and API contract, including optional
X-Deployment-Id reads with the "default" fallback and SolidJS consumption.
Ensure each task’s success criteria describes the delivered behavior and
measurable completion state, removing obsolete API or storage requirements while
keeping task 4.5.7 consistent.

In `@docs/solidjs-frontend.md`:
- Around line 207-209: Update the Playwright coverage description in
docs/solidjs-frontend.md to refer to the logs route rather than a logs dialog,
using wording consistent with the top-level /logs navigation path.

In `@Makefile`:
- Around line 107-108: Update the frontend-stub Make target to depend on
frontend-install so workspace dependencies are installed before running the Bun
development command; preserve the existing runtime command unchanged.

In `@src/channels/wasm/wrapper/tests/recording_store.rs`:
- Around line 74-80: Update the async write closure around the writes mutex in
the recording store fixture to propagate a poisoned-lock failure as
DatabaseError through its existing Result using ?. Remove the expect call while
preserving the key push and successful Ok result.

In `@src/channels/web/CLAUDE.md`:
- Around line 136-137: Update the serving references to reflect the moved
handler: in src/channels/web/CLAUDE.md lines 136-137, point routes_for() to
ui_assets.rs instead of static_files.rs; in docs/solidjs-frontend.md lines
162-163, likewise replace the static_files.rs serving reference with
ui_assets.rs.

In `@src/channels/web/handlers/feature_registry.rs`:
- Around line 41-48: Update deployment_id_from_headers to validate trimmed
deployment IDs with the same [a-z0-9_]+ character rules as is_valid_flag_name
and enforce an explicit maximum length before returning Some. Reject identifiers
that are empty, overlong, or contain any other characters so both registry reads
and writes receive only bounded valid keys.
- Around line 89-97: Update FeatureRegistry::hydrate to insert each store-loaded
override only when the deployment does not already contain that flag, preserving
values written by apply_flag_override after the hydration query began. Keep
deployment hydration state handling unchanged, and ensure later hydration
snapshots cannot overwrite existing entries.

In `@src/channels/web/handlers/features.rs`:
- Around line 120-153: Update FeatureFlagRegistry::hydrate in
feature_registry.rs to insert persisted overrides only for deployment flags that
are not already present, preserving values written by apply_flag_override during
concurrent hydration. Keep existing entries unchanged while retaining the
current hydration completion behavior.
- Around line 62-86: Cache the environment-variable overlay used by
features_handler instead of passing std::env::var directly to resolve_flags on
every request. Add a process-lifetime OnceLock-backed helper that evaluates the
required flag environment variables once, then reuse the cached overlay in
features_handler while preserving the existing overrides and unavailable-flag
resolution.

In `@src/channels/web/handlers/settings/tests.rs`:
- Around line 37-42: Replace every .unwrap() in the tests within this file with
.expect("...") using a concise, context-specific failure message, including the
fallible operations in body_string, migration setup, body construction, and JSON
deserialization. Preserve the existing test behavior and assertions.
- Around line 45-114: Replace the four duplicated 400-response tests with one
#[rstest] parameterized test covering each method, URI, headers, and body
combination. Preserve the existing request construction and
StatusCode::BAD_REQUEST assertion for all cases, including the deployment-header
and content-type variations, and remove the individual test functions.
- Around line 166-173: Remove the bare unsafe environment mutation from
put_feature_flag_then_get_reflects_override_without_restart. Use the shared
mutex-based environment guard from the existing test_utils/test_helpers
infrastructure when clearing FEATURE_FLAG_ROUTE_MEMORY, or inject an environment
reader into the tested flow so no real process environment mutation is needed;
do not rely on single-threaded test execution.

In `@src/channels/web/handlers/ui_assets.rs`:
- Around line 236-255: The route tests omit valid paths from coverage: add
“/logs” to the paths exercised by
solid_variant_serves_spa_shell_at_root_and_app_routes, and extend
legacy_variant_still_serves_the_handwritten_shell with assertions for
“/style.css”, “/app.js”, and “/favicon.ico”, matching the expected status,
content type, and response behavior of each legacy asset.
- Around line 73-75: Add a concise Rustdoc comment directly above the
public_routes() function describing that it builds and returns the UI asset
routes for the current UI variant, matching the existing documentation style for
public items in this module.
- Around line 214-233: Update the test helper get_path to replace all three
unwrap calls with expect calls that provide descriptive failure messages:
request construction, response execution, and body-byte extraction. Keep the
existing behavior and return values unchanged.
- Around line 32-38: Update ui_variant to obtain AXINITE_WEB_UI through an
injected EnvSource abstraction backed by mockable or the project’s existing
environment abstraction, rather than calling std::env::var directly. Adjust
callers and tests to provide the source explicitly, including a fixed value in
the test currently reading the real process environment, while preserving the
Legacy-for-“legacy” and Solid-default behavior.

In `@src/channels/web/static/solid/assets/app.js`:
- Line 1: Replace the hand-rolled escaping and tag allowlist in the markdown
renderer defined in markdown.ts with a vetted HTML sanitizer such as DOMPurify
before assigning rendered assistant responses to innerHTML. Update the
renderer’s sanitization flow to sanitize the generated markdown HTML, preserving
the existing output behavior while removing reliance on chained regex
replacements; do not modify the compiled bundle directly.

In `@src/channels/web/static/solid/assets/index.css`:
- Line 1: Update the stylelint configuration in stylelint.config.cjs to ignore
the generated assets under the static Solid bundle, including the
src/channels/web/static/solid/assets/** pattern and any other embedded
build-output directories. Keep linting focused on hand-authored styles under
web-src/axinite/src/styles/.

In `@src/channels/web/static/solid/locales/ar/common.ftl`:
- Line 169: Translate the remaining English job-status, URL-placeholder, and
extension-version values in both locale bundles:
src/channels/web/static/solid/locales/ar/common.ftl#L169-L169 and
src/channels/web/static/solid/locales/ja/common.ftl#L169-L169. Update the
corresponding entries, including jobs-item-comparison-elapsed, using accurate
Arabic and Japanese UI translations while preserving the Fluent keys and
formatting.
- Around line 376-382: Add a `[zero]` plural branch to the `chat-tools-used`
Fluent message for `$count`, using the correct Arabic zero-count wording; leave
the existing one, two, few, many, and other branches unchanged.

In `@src/channels/web/static/solid/locales/zh-CN/common.ftl`:
- Line 169: Translate the user-facing values for jobs-item-comparison-elapsed,
jobs-item-docs-elapsed, jobs-item-security-elapsed, and
extensions-version-preview in the zh-CN locale, replacing their English source
text with appropriate Simplified Chinese while preserving the existing Fluent
keys and formatting.

In `@src/db/libsql/settings.rs`:
- Around line 315-360: The
deployment_flag_round_trip_upserts_and_isolates_deployments test uses generic
unwraps that obscure which operation failed. Replace each unwrap in this test
with a specific expect message covering backend setup, migrations, flag writes,
and flag reads, including the deployment or flag name where applicable.

In `@tests/e2e/helpers.py`:
- Around line 139-147: Expand the docstring for the public async function
goto_route into full numpydoc format. Document the page, name, path, and timeout
parameters, and describe that wait_for_url matches the URL suffix pattern before
waiting for the route landmark; leave the navigation behavior unchanged.

In `@tests/e2e/scenarios/test_chat.py`:
- Line 1: Extract the duplicated message-composer interaction into a shared
send_chat_message helper in helpers.py, including the visibility wait. Remove
the local _send helpers from test_chat.py and test_html_injection.py, and
replace their calls with helpers.send_chat_message(page, text); also replace the
inline sequence in test_sse_reconnect.py with the shared helper.
- Around line 29-49: Update _assistant_markdown_contains to catch
playwright.async_api.TimeoutError specifically instead of bare Exception, and
chain the raised AssertionError from the caught error using “from err” while
preserving the existing diagnostic text and fallback inspection.

In `@tests/e2e/scenarios/test_extensions.py`:
- Line 1: Replace the ad-hoc polling in the extension scenarios with a shared
async wait_until helper in helpers.py accepting predicate, timeout_ms=2000, and
interval_ms=100, then use it for save_posts and approve_posts in
test_extensions.py. Remove the no-op wait_for_function calls, including the dead
call in test_tool_approval.py where card.wait_for(state="hidden", timeout=8000)
already provides sufficient waiting.
- Around line 422-437: Extract the duplicated stepper-circle text collection
from test_wasm_channel_stepper_active and test_wasm_channel_stepper_failed into
a shared _stepper_circle_texts(card) helper. Have both tests call the helper
while preserving their existing assertions and failure messages.

In `@tests/e2e/scenarios/test_skills.py`:
- Around line 46-76: Update test_skills_install_and_remove so a missing Remove
button explicitly skips with a clear reason instead of silently passing. After
clicking Remove, replace the fixed page.wait_for_timeout(3000) delay with
polling until the installed-card count is lower than installed_count, using the
suite’s existing retry/wait idiom while preserving the removal assertion.

In `@typos.local.toml`:
- Around line 170-183: Remove the listed project-owned architecture and
execution-plan document paths from the exclusions in typos.local.toml. Keep
maintained documentation covered by the spelling gate, and make this change only
in typos.local.toml rather than generated spelling configuration.

In `@typos.toml`:
- Around line 27-38: Move the listed spelling exceptions, including the
additional referenced ranges, from typos.toml into typos.local.toml. Leave
typos.toml unchanged as generated configuration, and preserve the existing
exception entries and patterns when relocating them.

In `@web-src/axinite/public/locales/ja/common.ftl`:
- Around line 169-178: Complete the Japanese locale by translating the English
values for jobs-item-comparison-elapsed, jobs-item-docs-elapsed, and
jobs-item-security-elapsed, plus the remaining “or” and “preview” entries
elsewhere in the bundle. Update the locale-completeness exposure only after all
Japanese UI values are translated, and include the required Triage paragraph for
the grammatical corrections.

In `@web-src/axinite/public/locales/nl/common.ftl`:
- Around line 339-350: Update the `skills-item-frontend-a11y-file1` through
`skills-item-frontend-a11y-file8` localization keys to use the established
`-file-N` naming pattern, inserting the hyphen before each numeric suffix while
preserving their values.

In `@web-src/axinite/public/locales/pl/common.ftl`:
- Around line 339-350: Update the frontend-a11y localization keys in this locale
from skills-item-frontend-a11y-file1..8 to the established
skills-item-frontend-a11y-file-1..8 pattern, preserving all translated values
and file ordering.

In `@web-src/axinite/src/app/router.tsx`:
- Around line 18-24: Update both `/chat` redirects, including the
`NotFoundRedirect` component and the other chat redirect in the router, to pass
the navigation option that replaces the current history entry. Preserve the
existing redirect destinations and unmatched-path warning behavior.

In `@web-src/axinite/src/components/auth-gate.tsx`:
- Around line 78-88: Remove the redundant unauthorized-and-stored branch after
probeGateway in the auth gate. Let applyProbe(result, false) handle the result
directly, preserving the existing unauthorized behavior and updating the nearby
comment to document that hadToken is false regardless of stored.
- Around line 17-35: Update probeGateway in
web-src/axinite/src/components/auth-gate.tsx (lines 17-35) and fetchJson in
web-src/axinite/src/lib/tee.ts (lines 74-80) to use the same 5-second
AbortController timeout behavior as client.ts request(). Pass each controller’s
signal to fetch and ensure timeout cleanup remains correct, while preserving
existing response handling and error behavior.

In `@web-src/axinite/src/components/logs-preview.tsx`:
- Around line 196-198: Update the levelMutation configuration to invalidate or
refetch the ["logs", "level"] query after setLogLevel succeeds, using the
existing query client and success callback pattern. Ensure the controlled select
bound to level.data?.level reflects the confirmed server value.

In `@web-src/axinite/src/components/memory-preview.tsx`:
- Around line 247-251: Rename the query binding `document` in `MemoryPreview` to
`memoryDocument` (or `activeDocument`) and update all readers, including the
usages around lines 254 and 318, so the component no longer shadows the global
DOM `document`.

In `@web-src/axinite/src/global.d.ts`:
- Around line 24-30: Update the FluentBackend declaration to fully satisfy the
BackendModule contract: make init non-optional and add the required read method
with the appropriate signature. Keep implements
BackendModule<FluentBackendOptions> only after both methods are declared;
otherwise remove the implements clause.

In `@web-src/axinite/src/lib/api/chat.ts`:
- Around line 85-95: Wrap the JSON parsing and listener invocation in the SSE
event handler registered by the eventTypes loop with a try/catch, and call the
existing optional onError callback when parsing or dispatching a malformed frame
throws. Preserve the non-string data guard and normal listener behavior for
valid gateway frames.

In `@web-src/axinite/src/lib/api/extensions.ts`:
- Around line 36-54: Encode every dynamic resource path segment with
encodeURIComponent before interpolation: update activateExtension,
removeExtension, fetchExtensionSetup, and submitExtensionSetup in
web-src/axinite/src/lib/api/extensions.ts (lines 36-54); all id-based routes in
web-src/axinite/src/lib/api/jobs.ts (lines 21-54); detail, runs, trigger,
toggle, and deletion routes in web-src/axinite/src/lib/api/routines.ts (lines
19-39); and the deletion route in web-src/axinite/src/lib/api/skills.ts (lines
26-27). Ensure encoded names and IDs cannot alter route boundaries or
query/fragment parsing.

In `@web-src/axinite/src/lib/api/logs.ts`:
- Around line 10-13: Update setLogLevel to return the postJson promise directly
and remove the catch fallback, so rejected log-level updates propagate to
callers while successful responses remain unchanged.

In `@web-src/axinite/src/lib/markdown.ts`:
- Around line 13-20: Replace the hand-rolled entity replacements in escapeHtml
with a vetted HTML-encoding utility, preserving escaping for text/element
content and the function’s existing string contract. Use the project’s
established dependency or encoding helper rather than extending the manual
replacement list.

In `@web-src/axinite/src/lib/restart.ts`:
- Around line 136-144: In the sendRestart failure handler, remove the assignment
that sets finished to true so the restart controller remains retryable. Keep the
existing cleanup and deps.onPhase("idle") behavior, allowing start() to run
again after a failed command.

In `@web-src/axinite/src/styles/index.css`:
- Line 1: Remove the remote Google Fonts import from the stylesheet and
self-host Buenard and Molengo by bundling their font files with the embedded
assets. Add local `@font-face` declarations for both families, covering the
required weights and referencing the bundled asset paths so the SPA loads
correctly offline without third-party requests.

In `@web-src/axinite/src/styles/semantic.css`:
- Line 945: Update web-src/axinite/src/styles/semantic.css at lines 945-945,
2260-2260, 2377-2377, and 2576-2576: replace the deprecated clip declaration in
.catalogue-table__caption with clip-path: inset(50%) while preserving the
visually-hidden pattern, and lowercase currentColor to currentcolor in
.shell-restart__icon, .stepper-circle, and .jobs-file-tree__twist.

In `@web-src/axinite/tests/extensions-preview.behaviour.test.tsx`:
- Line 1: Extract the duplicated extension API mock map, reset helper, mock
factory, and immutable firecrawl fixture into a shared
tests/support/extension-api-mocks.ts module. Update
extensions-preview.behaviour.test.tsx and extensions-preview.a11y.test.tsx to
import and reuse these shared symbols, while keeping the behaviour spec’s
installedExtensions and registryEntries mutations local and using the shared
firecrawl fixture in the a11y spec.

In `@web-src/axinite/tests/tee-attestation.behaviour.test.tsx`:
- Around line 115-118: Update the test setup around the navigator.clipboard
definition to capture its original property descriptor before replacement, then
restore that descriptor in an afterEach hook. Ensure cleanup removes the test
override when no original descriptor existed, and retain the existing mock
restoration behavior.

In `@web-src/docs/axinite-v2a-frontend-architecture.md`:
- Around line 23-35: Update the architecture description to state that the
default SolidJS SPA assets are embedded in and served by the Axinite binary
through the gateway UI, rather than deployed as standalone static output.
Document the handwritten shell as the rollback path selected by
AXINITE_WEB_UI=legacy, while preserving the existing SolidJS-versus-legacy
boundary and one-binary deployment model.

In `@web-src/docs/daisyui-v5-guide.md`:
- Line 3: Remove the document-wide MD013 suppression from the markdownlint
directive in daisyui-v5-guide.md, then reflow prose paragraphs and bullet items
to 80 columns while preserving tables, headings, and 120-column code blocks
according to workspace configuration. Validate the updated document with make
markdownlint.

In `@web-src/docs/enforcing-semantic-tailwind-best-practice.md`:
- Around line 1-9: Rewrite
web-src/docs/enforcing-semantic-tailwind-best-practice.md (lines 1-9) for the
actual web-src/axinite workspace, replacing Corbusier, Wildside, and
axinite-mockup references with Axinite paths, tools, locales, and
repository-relative verification commands. Update
web-src/docs/data-model-driven-card-architecture.md (lines 7-15) to describe
current Axinite architecture and (line 58) align LocaleCode with supported
Axinite locales. Replace the personal absolute path in
web-src/docs/execplans/mock-backend.md (lines 494-497) with repository-relative
instructions, update web-src/docs/execplans/solidjs-translation.md (lines
200-245) to use web-src/axinite, and revise
web-src/docs/high-velocity-accessibility-first-component-testing.md (lines
11-16) to document the configured Vitest test and accessibility lanes instead of
a separate Node/tsgo harness.

In `@web-src/docs/tailwind-v3-v4-migration-guide.md`:
- Around line 26-30: Update the migration guide example to use the standard
spacing utility lg:p-4 instead of lg:p-(--spacing-4), since only the base
--spacing token is defined. Do not introduce a custom spacing variable unless
the example explicitly declares --spacing-4 in `@theme`.

In `@web-src/docs/tailwind-v4-guide.md`:
- Around line 826-830: Update the Overflow Wrap utility list in the Tailwind v4
guide to use the generated class names wrap-normal, wrap-break-word, and
wrap-anywhere instead of the overflow-wrap-prefixed names.

In `@web-src/docs/v2a-front-end-stack.md`:
- Around line 3-19: Update web-src/docs/v2a-front-end-stack.md lines 3-19 to
document the implemented Axinite SolidJS SPA, removing df12 Productions,
Wildside, Corbusier, and static-prototype framing. Also update
web-src/docs/solidjs-tailwind-with-bun.md lines 3-5 to describe the current SPA
implementation rather than a future migration path.

In `@web-src/mock-backend/src/preview-server.ts`:
- Around line 28-38: Remove the repeated path.join(distDir, relative,
"index.html") entry from the extensionless branch of the candidate list, while
preserving the unconditional candidate and all other fallback paths.

In `@web-src/mock-backend/src/state.ts`:
- Around line 218-226: Update subscribeToLogs to replay the newest 25 entries in
chronological oldest-to-newest order by replacing the current logs slice
iteration with the equivalent of taking the first 25 newest-first entries and
reversing them before sending. Preserve subscriber registration and unsubscribe
behavior.

In `@web-src/scripts/check-classlist-length.ts`:
- Around line 6-23: Extract the shared TSX class-attribute scanning logic into a
helper such as scan-class-attributes.ts, including the glob, file reading, and
one regex that supports surrounding whitespace, newlines, and empty class
values. Update check-classlist-length.ts and find-near-duplicate-classes.ts to
import and use this helper; apply the change at
web-src/scripts/check-classlist-length.ts lines 6-23 and
web-src/scripts/find-near-duplicate-classes.ts lines 5-21 so both checks process
identical class attributes.
- Around line 2-7: Resolve the Bun compatibility mismatch by either raising the
minimum Bun version in web-src/package.json to one that supports node:fs
globSync, or replacing globSync usage with a compatible alternative. Apply the
chosen fix to web-src/scripts/check-classlist-length.ts and
web-src/scripts/find-near-duplicate-classes.ts so both lint scripts start
successfully under the supported runtime.

In `@web-src/scripts/dev.ts`:
- Around line 3-4: Validate the values assigned to apiPort and
defaultPreviewPort before they are used, rejecting non-numeric or otherwise
invalid port environment values with a clear failure message. Preserve the
existing defaults when variables are unset, and ensure invalid input fails fast
rather than propagating NaN to child environment variables.
- Around line 129-163: Update stopAll to accept an exit code, preserving 0 for
SIGINT and SIGTERM while using a non-zero code when called from the unexpected
child-exit branch after logging the failure. Pass the appropriate code at each
call site so child crashes propagate failure through dev.ts.

---

Outside diff comments:
In `@tests/e2e/scenarios/test_extensions.py`:
- Around line 1-478: Split tests/extensions scenario file into multiple
pytest-discoverable test_*.py modules at the existing Group A-H boundaries,
keeping related helpers and fixture data with the groups that use them. Ensure
each resulting module stays under 400 logical lines, preserves all test behavior
and imports, and requires no conftest changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2f82a1d2-0be1-42ba-a734-da8aa025cfe5

📥 Commits

Reviewing files that changed from the base of the PR and between 73e9cbf and 3edd57b.

⛔ Files ignored due to path filters (3)
  • src/channels/web/static/solid/assets/axinite32.ico is excluded by !**/*.ico
  • web-src/axinite/assets/icons/axinite32.ico is excluded by !**/*.ico
  • web-src/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (201)
  • .codescene/code-health-rules.json
  • .github/workflows/frontend.yml
  • Makefile
  • README.md
  • docs/execplans/adopt-solidjs-ui-followups.md
  • docs/execplans/adopt-solidjs-ui.md
  • docs/front-end-architecture.md
  • docs/rfcs/0009-feature-flags-frontend.md
  • docs/roadmap.md
  • docs/solidjs-frontend.md
  • migrations/V18__feature_flag_overrides.sql
  • migrations/libsql_schema.sql
  • src/bootstrap/tests/migration_support.rs
  • src/channels/wasm/wrapper/tests/dispatch.rs
  • src/channels/wasm/wrapper/tests/mod.rs
  • src/channels/wasm/wrapper/tests/recording_store.rs
  • src/channels/web/CLAUDE.md
  • src/channels/web/handlers/feature_registry.rs
  • src/channels/web/handlers/features.rs
  • src/channels/web/handlers/mod.rs
  • src/channels/web/handlers/settings.rs
  • src/channels/web/handlers/settings/tests.rs
  • src/channels/web/handlers/static_files.rs
  • src/channels/web/handlers/ui_assets.rs
  • src/channels/web/mod.rs
  • src/channels/web/server.rs
  • src/channels/web/server/tests/fixtures.rs
  • src/channels/web/static/solid/assets/app.js
  • src/channels/web/static/solid/assets/index.css
  • src/channels/web/static/solid/index.html
  • src/channels/web/static/solid/locales/ar/common.ftl
  • src/channels/web/static/solid/locales/de/common.ftl
  • src/channels/web/static/solid/locales/en-GB/common.ftl
  • src/channels/web/static/solid/locales/fr/common.ftl
  • src/channels/web/static/solid/locales/hi/common.ftl
  • src/channels/web/static/solid/locales/it/common.ftl
  • src/channels/web/static/solid/locales/ja/common.ftl
  • src/channels/web/static/solid/locales/nl/common.ftl
  • src/channels/web/static/solid/locales/pl/common.ftl
  • src/channels/web/static/solid/locales/zh-CN/common.ftl
  • src/channels/web/test_helpers.rs
  • src/channels/web/ws/tests.rs
  • src/db/CLAUDE.md
  • src/db/forwarders.rs
  • src/db/libsql/settings.rs
  • src/db/libsql_migrations.rs
  • src/db/postgres/settings.rs
  • src/db/settings.rs
  • src/db/traits/settings.rs
  • src/history/store/settings.rs
  • src/reload/config_loader.rs
  • src/startup/unix_runtime.rs
  • src/testing/null_db/capturing_store/delegation.rs
  • src/testing/null_db/null_database/settings_store.rs
  • tests/channels/openai_compat/helpers.rs
  • tests/channels/openai_compat/validation.rs
  • tests/channels/ws_gateway/helpers.rs
  • tests/e2e/.gitignore
  • tests/e2e/CLAUDE.md
  • tests/e2e/conftest.py
  • tests/e2e/helpers.py
  • tests/e2e/mock_llm.py
  • tests/e2e/scenarios/test_chat.py
  • tests/e2e/scenarios/test_connection.py
  • tests/e2e/scenarios/test_extensions.py
  • tests/e2e/scenarios/test_html_injection.py
  • tests/e2e/scenarios/test_skills.py
  • tests/e2e/scenarios/test_sse_reconnect.py
  • tests/e2e/scenarios/test_tool_approval.py
  • tests/trybuild/settings_compat.rs
  • typos.local.toml
  • typos.toml
  • web-src/.gitignore
  • web-src/.markdownlint-cli2.jsonc
  • web-src/AGENTS.md
  • web-src/CONTRIBUTING.md
  • web-src/Makefile
  • web-src/axinite/index.html
  • web-src/axinite/public/locales/ar/common.ftl
  • web-src/axinite/public/locales/de/common.ftl
  • web-src/axinite/public/locales/en-GB/common.ftl
  • web-src/axinite/public/locales/fr/common.ftl
  • web-src/axinite/public/locales/hi/common.ftl
  • web-src/axinite/public/locales/it/common.ftl
  • web-src/axinite/public/locales/ja/common.ftl
  • web-src/axinite/public/locales/nl/common.ftl
  • web-src/axinite/public/locales/pl/common.ftl
  • web-src/axinite/public/locales/zh-CN/common.ftl
  • web-src/axinite/src/app/providers.tsx
  • web-src/axinite/src/app/router.tsx
  • web-src/axinite/src/components/app-shell.tsx
  • web-src/axinite/src/components/auth-gate.tsx
  • web-src/axinite/src/components/chat-cards.tsx
  • web-src/axinite/src/components/chat-preview.tsx
  • web-src/axinite/src/components/debug-flag-panel.tsx
  • web-src/axinite/src/components/extension-pairing.tsx
  • web-src/axinite/src/components/extensions-preview.tsx
  • web-src/axinite/src/components/jobs-preview.tsx
  • web-src/axinite/src/components/jobs/file-tree.tsx
  • web-src/axinite/src/components/jobs/format.ts
  • web-src/axinite/src/components/jobs/job-detail.tsx
  • web-src/axinite/src/components/locale-picker.tsx
  • web-src/axinite/src/components/logs-preview.tsx
  • web-src/axinite/src/components/memory-preview.tsx
  • web-src/axinite/src/components/restart-control.tsx
  • web-src/axinite/src/components/route-page.tsx
  • web-src/axinite/src/components/routines-preview.tsx
  • web-src/axinite/src/components/skills-preview.tsx
  • web-src/axinite/src/components/tee-attestation.tsx
  • web-src/axinite/src/components/wasm-channel-stepper.tsx
  • web-src/axinite/src/global.d.ts
  • web-src/axinite/src/lib/api/chat.ts
  • web-src/axinite/src/lib/api/client.ts
  • web-src/axinite/src/lib/api/contracts.ts
  • web-src/axinite/src/lib/api/extensions.ts
  • web-src/axinite/src/lib/api/gateway.ts
  • web-src/axinite/src/lib/api/jobs.ts
  • web-src/axinite/src/lib/api/logs.ts
  • web-src/axinite/src/lib/api/memory.ts
  • web-src/axinite/src/lib/api/pairing.ts
  • web-src/axinite/src/lib/api/routines.ts
  • web-src/axinite/src/lib/api/skills.ts
  • web-src/axinite/src/lib/auth/token.ts
  • web-src/axinite/src/lib/base-path.ts
  • web-src/axinite/src/lib/connection-status.ts
  • web-src/axinite/src/lib/feature-flags/registry.ts
  • web-src/axinite/src/lib/feature-flags/runtime.tsx
  • web-src/axinite/src/lib/i18n/provider.tsx
  • web-src/axinite/src/lib/i18n/runtime.ts
  • web-src/axinite/src/lib/i18n/supported-locales.ts
  • web-src/axinite/src/lib/markdown.ts
  • web-src/axinite/src/lib/restart.ts
  • web-src/axinite/src/lib/route-config.ts
  • web-src/axinite/src/lib/string-case.ts
  • web-src/axinite/src/lib/tee.ts
  • web-src/axinite/src/lib/test-hooks.ts
  • web-src/axinite/src/main.tsx
  • web-src/axinite/src/styles/index.css
  • web-src/axinite/src/styles/semantic.css
  • web-src/axinite/tests/api-contract-alignment.test.ts
  • web-src/axinite/tests/app-shell.a11y.test.tsx
  • web-src/axinite/tests/app-shell.behaviour.test.tsx
  • web-src/axinite/tests/auth-gate.behaviour.test.tsx
  • web-src/axinite/tests/auth-token.test.ts
  • web-src/axinite/tests/base-path.test.ts
  • web-src/axinite/tests/chat-cards.test.ts
  • web-src/axinite/tests/chat-preview.behaviour.test.tsx
  • web-src/axinite/tests/e2e/app-shell.pw.ts
  • web-src/axinite/tests/extension-pairing.behaviour.test.tsx
  • web-src/axinite/tests/extensions-preview.a11y.test.tsx
  • web-src/axinite/tests/extensions-preview.behaviour.test.tsx
  • web-src/axinite/tests/feature-flags.test.ts
  • web-src/axinite/tests/jobs-preview.behaviour.test.tsx
  • web-src/axinite/tests/logs-preview.behaviour.test.tsx
  • web-src/axinite/tests/mock-backend-contract.test.ts
  • web-src/axinite/tests/mock-backend-streaming-routes.test.ts
  • web-src/axinite/tests/restart-control.behaviour.test.tsx
  • web-src/axinite/tests/restart.test.ts
  • web-src/axinite/tests/setup-vitest-a11y.ts
  • web-src/axinite/tests/setup-vitest.ts
  • web-src/axinite/tests/support/i18n-test-runtime.ts
  • web-src/axinite/tests/support/test-providers.tsx
  • web-src/axinite/tests/supported-locales.test.ts
  • web-src/axinite/tests/tee-attestation.behaviour.test.tsx
  • web-src/axinite/tests/tee.test.ts
  • web-src/axinite/tests/test-hooks.behaviour.test.tsx
  • web-src/axinite/tests/wasm-channel-stepper.test.ts
  • web-src/biome.jsonc
  • web-src/docs/axinite-v2a-frontend-architecture.md
  • web-src/docs/building-accessible-and-responsive-progressive-web-applications.md
  • web-src/docs/daisyui-v5-guide.md
  • web-src/docs/data-model-driven-card-architecture.md
  • web-src/docs/enforcing-semantic-tailwind-best-practice.md
  • web-src/docs/execplans/mock-backend.md
  • web-src/docs/execplans/solidjs-translation.md
  • web-src/docs/high-velocity-accessibility-first-component-testing.md
  • web-src/docs/pure-accessible-and-localizable-solidjs-components.md
  • web-src/docs/semantic-tailwind-with-daisyui-best-practice.md
  • web-src/docs/solidjs-tailwind-with-bun.md
  • web-src/docs/tailwind-v3-v4-migration-guide.md
  • web-src/docs/tailwind-v4-guide.md
  • web-src/docs/v2a-front-end-stack.md
  • web-src/mock-backend/src/fixtures.ts
  • web-src/mock-backend/src/preview-server.ts
  • web-src/mock-backend/src/server.ts
  • web-src/mock-backend/src/state-helpers.ts
  • web-src/mock-backend/src/state.ts
  • web-src/mock-backend/src/streaming-routes.ts
  • web-src/package.json
  • web-src/playwright.config.ts
  • web-src/scripts/check-classlist-length.ts
  • web-src/scripts/check-fluent-coverage.ts
  • web-src/scripts/check-fluent-vars.ts
  • web-src/scripts/dev.ts
  • web-src/scripts/find-near-duplicate-classes.ts
  • web-src/tools/semgrep-semantic.yml
  • web-src/tools/stylelint.config.cjs
  • web-src/tsconfig.json
  • web-src/vite.config.ts
  • web-src/vitest.a11y.config.ts
  • web-src/vitest.config.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • leynos/ironclaw (auto-detected)
  • leynos/memoryd (auto-detected)
💤 Files with no reviewable changes (1)
  • src/channels/web/handlers/static_files.rs

Comment on lines +231 to +240
Remaining follow-up work:

- Rewrite the Python e2e scenarios (`tests/e2e/`) against the SolidJS DOM
route-by-route (RFC 0018 Stage 4) and then retire the legacy shell and its
assets (Stage 5), including `tests/web_static_app.test.mjs`.
- Implement the RFC 0009 settings-table/deployment-scoped flag layer beneath
the env-var resolution in `handlers/features.rs`.
- Close the remaining UI parity gaps catalogued in
`docs/solidjs-pwa-gap-analysis.md` (logs as a route, restart/TEE/pairing
surfaces, chat media, jobs detail fidelity).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Refresh the completed plan’s remaining-work section.

The listed follow-ups—SolidJS E2E migration and deployment-scoped feature flags—are delivered by the follow-up plan. Remove them from “Remaining follow-up work” or label this section as historical, while retaining only genuinely outstanding work.

As per coding guidelines, documentation under docs/ is the source of truth and must reflect completed requirements.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/execplans/adopt-solidjs-ui.md` around lines 231 - 240, Update the
“Remaining follow-up work” section in the completed plan to remove the delivered
SolidJS E2E migration and deployment-scoped feature-flag items, or explicitly
mark the section as historical. Retain only genuinely outstanding UI parity
work, including the referenced gap-analysis items.

Source: Coding guidelines

Comment on lines +497 to +518
- **Storage**: deployment-scoped overrides live in a dedicated
`feature_flag_overrides` table (primary key `(deployment_id, flag_name)`;
Postgres migration `V18`, libsql incremental 18) rather than a
deployment-aware extension of the `settings` table. This keeps the
`(user_id, key)` settings contract untouched and avoids a libsql
table rebuild; the API surface (`feature_flag:` key prefix,
`X-Deployment-Id` header) is unchanged.
- **Reads default the deployment**: `GET /api/features` treats
`X-Deployment-Id` as optional and resolves to the `"default"`
deployment when absent, because the browser boot fetch has no
deployment identity source. Writes require the header, as specified.
- **Settings API access to flag keys**: `GET` and `DELETE` of
`feature_flag:` keys through `/api/settings` return 400, directing
callers to `GET /api/features`; flag rows never enter the user-scoped
settings table.
- **Subsystem-availability defaults** are implemented as a
disable-only layer: when a flag's backing subsystem is absent from
`GatewayState` (jobs and routines runtimes, extension manager, skill
registry, log broadcaster), the flag defaults to `false`; presence of
a subsystem falls through to the compiled default rather than
enabling a flag early. Environment variables and operator overrides
still take precedence.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Synchronize the feature-flag documentation with the shipped contract.

The implementation uses a dedicated override table, optional deployment headers for reads, and SolidJS consumption, but earlier normative documentation still describes different behaviour.

  • docs/rfcs/0009-feature-flags-frontend.md#L497-L518: revise or explicitly supersede the earlier storage, header, and subsystem-default requirements.
  • docs/roadmap.md#L1163-L1172: correct tasks 4.5.3–4.5.5 before retaining 4.5.7 as the remaining SSE work.
🧰 Tools
🪛 LanguageTool

[formatting] ~506-~506: If the ‘because’ clause is essential to the meaning, do not use a comma before the clause.
Context: ...the "default" deployment when absent, because the browser boot fetch has no deploym...

(COMMA_BEFORE_BECAUSE)

📍 Affects 2 files
  • docs/rfcs/0009-feature-flags-frontend.md#L497-L518 (this comment)
  • docs/roadmap.md#L1163-L1172
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/rfcs/0009-feature-flags-frontend.md` around lines 497 - 518, Update
docs/rfcs/0009-feature-flags-frontend.md:497-518 to explicitly supersede earlier
requirements with the shipped dedicated override table, optional deployment
header for reads, required header for writes, and disable-only subsystem
defaults. Update docs/roadmap.md:1163-1172 so tasks 4.5.3–4.5.5 reflect the
implemented contract, leaving task 4.5.7 as the remaining SSE work.

Source: Coding guidelines

Comment thread docs/roadmap.md
Comment on lines +1163 to +1172
- [ ] 4.5.7. Emit a `feature_flags_changed` Server-Sent Events (SSE) event
when a deployment-scoped override changes. Requires 4.5.3.
- See
[RFC 0009 §Open questions](./rfcs/0009-feature-flags-frontend.md#open-questions).
- Success: applying a `feature_flag:` override through the settings API
broadcasts a `feature_flags_changed` event (carrying the deployment
identifier) on the chat SSE stream, the SolidJS front end invalidates its
cached flag map and re-fetches `GET /api/features` on receipt, connected
browsers reflect the new flag state without a page reload, and the mock
backend mirrors the event for stub-driven tests.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Correct the preceding 4.5.x contracts before tracking 4.5.7.

Update tasks 4.5.3–4.5.5 to describe feature_flag_overrides, optional X-Deployment-Id on reads with the "default" fallback, and SolidJS consumption. Otherwise the roadmap presents obsolete API and storage requirements alongside this new SSE task.

As per coding guidelines, roadmap documentation must describe the delivered capability and its measurable completion state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/roadmap.md` around lines 1163 - 1172, Update roadmap tasks 4.5.3–4.5.5
to use the current feature_flag_overrides storage and API contract, including
optional X-Deployment-Id reads with the "default" fallback and SolidJS
consumption. Ensure each task’s success criteria describes the delivered
behavior and measurable completion state, removing obsolete API or storage
requirements while keeping task 4.5.7 consistent.

Source: Coding guidelines

Comment thread docs/solidjs-frontend.md
Comment on lines +207 to +209
- `web-src` Playwright (`bun run test:e2e` in `web-src/`): boots the full
stub stack and exercises navigation, locales, the logs dialog, and the
debug flag panel.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Rename the documented logs surface from a dialog to a route.

The logs UI is now exercised at the top-level /logs route. Replace “logs dialog” so the documented Playwright coverage matches the shipped navigation model.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/solidjs-frontend.md` around lines 207 - 209, Update the Playwright
coverage description in docs/solidjs-frontend.md to refer to the logs route
rather than a logs dialog, using wording consistent with the top-level /logs
navigation path.

Comment on lines +74 to +80
Box::pin(async move {
self.writes
.lock()
.expect("settings writes lock poisoned")
.push(key.to_string());
Ok(())
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Propagate the poisoned-lock error.

At Line 77, map the mutex failure to DatabaseError and return it through the
existing Result instead of panicking from this shared fixture.

As per coding guidelines, shared fixtures must propagate errors with Result
and ?; based on learnings, restrict .expect(...) to test boundaries or
assertion helpers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/channels/wasm/wrapper/tests/recording_store.rs` around lines 74 - 80,
Update the async write closure around the writes mutex in the recording store
fixture to propagate a poisoned-lock failure as DatabaseError through its
existing Result using ?. Remove the expect call while preserving the key push
and successful Ok result.

Sources: Coding guidelines, Learnings

padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the stylelint errors in semantic.css so the lint gate passes. Static analysis reports four failures in this file with one shared cause — non-conformant CSS that stylelint 17.14.0 rejects.

  • web-src/axinite/src/styles/semantic.css#L945-L945: replace the deprecated clip: rect(0, 0, 0, 0) in .catalogue-table__caption with clip-path: inset(50%) (keep the rest of the visually-hidden pattern).
  • web-src/axinite/src/styles/semantic.css#L2260-L2260: lowercase currentColor to currentcolor on .shell-restart__icon.
  • web-src/axinite/src/styles/semantic.css#L2377-L2377: lowercase currentColor to currentcolor on .stepper-circle.
  • web-src/axinite/src/styles/semantic.css#L2576-L2576: lowercase currentColor to currentcolor on .jobs-file-tree__twist.
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 945-945: Deprecated property "clip" (property-no-deprecated)

(property-no-deprecated)

📍 Affects 1 file
  • web-src/axinite/src/styles/semantic.css#L945-L945 (this comment)
  • web-src/axinite/src/styles/semantic.css#L2260-L2260
  • web-src/axinite/src/styles/semantic.css#L2377-L2377
  • web-src/axinite/src/styles/semantic.css#L2576-L2576
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-src/axinite/src/styles/semantic.css` at line 945, Update
web-src/axinite/src/styles/semantic.css at lines 945-945, 2260-2260, 2377-2377,
and 2576-2576: replace the deprecated clip declaration in
.catalogue-table__caption with clip-path: inset(50%) while preserving the
visually-hidden pattern, and lowercase currentColor to currentcolor in
.shell-restart__icon, .stepper-circle, and .jobs-file-tree__twist.

Source: Linters/SAST tools

@@ -0,0 +1,193 @@
import { render, screen, waitFor, within } from "@solidjs/testing-library";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared extension-API mock scaffolding into a test-support helper.

Both extension-preview specs declare and reset the identical eight-function vi.hoisted mock map, the same vi.mock("@/lib/api/extensions", ...) re-export block, and a near-identical firecrawl fixture. One shared helper module removes ~35 duplicated lines per file and keeps future mock additions in sync automatically.

  • web-src/axinite/tests/extensions-preview.behaviour.test.tsx#L23-43: import the shared extensionApiMocks map and vi.mock factory from a new tests/support/extension-api-mocks.ts instead of redeclaring them; keep the per-test installedExtensions/registryEntries mutation local since it drives the removal-flow assertions.
  • web-src/axinite/tests/extensions-preview.a11y.test.tsx#L11-31: same import swap; this file's fixed firecrawl fixture (L53-101) can consume the shared default fixture directly since it never mutates state.
♻️ Proposed shared helper
// web-src/axinite/tests/support/extension-api-mocks.ts
import { vi } from "vitest";

export const extensionApiMocks = vi.hoisted(() => ({
  activateExtension: vi.fn(),
  fetchExtensionRegistry: vi.fn(),
  fetchExtensions: vi.fn(),
  fetchExtensionSetup: vi.fn(),
  fetchExtensionTools: vi.fn(),
  installExtension: vi.fn(),
  removeExtension: vi.fn(),
  submitExtensionSetup: vi.fn(),
}));

export function resetExtensionApiMocks(): void {
  for (const mockFn of Object.values(extensionApiMocks)) {
    mockFn.mockReset();
  }
}

Each spec then does:

-const extensionApiMocks = vi.hoisted(() => ({ ... }));
-
-vi.mock("`@/lib/api/extensions`", () => ({ ... }));
+import { extensionApiMocks, resetExtensionApiMocks } from "./support/extension-api-mocks";
+
+vi.mock("`@/lib/api/extensions`", () => extensionApiMocks);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-src/axinite/tests/extensions-preview.behaviour.test.tsx` at line 1,
Extract the duplicated extension API mock map, reset helper, mock factory, and
immutable firecrawl fixture into a shared tests/support/extension-api-mocks.ts
module. Update extensions-preview.behaviour.test.tsx and
extensions-preview.a11y.test.tsx to import and reuse these shared symbols, while
keeping the behaviour spec’s installedExtensions and registryEntries mutations
local and using the shared firecrawl fixture in the a11y spec.

Comment on lines +115 to +118
Object.defineProperty(navigator, "clipboard", {
configurable: true,
value: { writeText },
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Restore navigator.clipboard after the test.

Preserve its descriptor and restore it in afterEach; the direct property
replacement survives vi.restoreAllMocks() and can leak into later tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-src/axinite/tests/tee-attestation.behaviour.test.tsx` around lines 115 -
118, Update the test setup around the navigator.clipboard definition to capture
its original property descriptor before replacement, then restore that
descriptor in an afterEach hook. Ensure cleanup removes the test override when
no original descriptor existed, and retain the existing mock restoration
behavior.

Comment on lines +28 to +38
return [
path.join(distDir, relative),
path.join(distDir, relative, "index.html"),
...(hasExtension
? []
: [
path.join(distDir, `${relative}.html`),
path.join(distDir, relative, "index.html"),
path.join(distDir, "index.html"),
]),
];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the duplicated index.html candidate.

path.join(distDir, relative, "index.html") is already emitted on the unconditional entry above, then repeated inside the extensionless branch. Remove the duplicate to keep the candidate list minimal.

Triage: none.

♻️ Proposed tidy
     ...(hasExtension
       ? []
       : [
           path.join(distDir, `${relative}.html`),
-          path.join(distDir, relative, "index.html"),
           path.join(distDir, "index.html"),
         ]),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return [
path.join(distDir, relative),
path.join(distDir, relative, "index.html"),
...(hasExtension
? []
: [
path.join(distDir, `${relative}.html`),
path.join(distDir, relative, "index.html"),
path.join(distDir, "index.html"),
]),
];
return [
path.join(distDir, relative),
path.join(distDir, relative, "index.html"),
...(hasExtension
? []
: [
path.join(distDir, `${relative}.html`),
path.join(distDir, "index.html"),
]),
];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-src/mock-backend/src/preview-server.ts` around lines 28 - 38, Remove the
repeated path.join(distDir, relative, "index.html") entry from the extensionless
branch of the candidate list, while preserving the unconditional candidate and
all other fallback paths.

Comment on lines +218 to +226
subscribeToLogs(subscriber: EventSubscriber<LogEntry>): () => void {
this.logSubscribers.add(subscriber);
for (const entry of this.logs.slice(-25)) {
subscriber.send(entry);
}
return () => {
this.logSubscribers.delete(subscriber);
};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- state.ts ---\n'
sed -n '180,260p' web-src/mock-backend/src/state.ts

printf '\n--- find logs preview consumer/tests ---\n'
fd -a 'logs-preview' web-src/axinite/src web-src/mock-backend/src tests . || true

printf '\n--- search for subscribeToLogs / log replay / logs preview ---\n'
rg -n "subscribeToLogs|slice\\(-25\\)|logs-preview|replay|log(s)? preview|EventSubscriber<LogEntry>" web-src/axinite web-src/mock-backend tests . -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' || true

Repository: leynos/axinite

Length of output: 243


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the logs preview component and any tests that define ordering.
for f in \
  web-src/axinite/src/components/logs-preview.tsx \
  web-src/axinite/src/components/logs-preview.test.tsx \
  web-src/axinite/src/components/logs-preview.spec.tsx \
  web-src/axinite/src/components/*.test.tsx \
  web-src/axinite/src/components/*.spec.tsx
do
  [ -f "$f" ] && { printf '\n--- %s ---\n' "$f"; sed -n '1,260p' "$f"; }
done

Repository: leynos/axinite

Length of output: 7738


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- web-src/mock-backend/src/state.ts ---'
sed -n '180,260p' web-src/mock-backend/src/state.ts

printf '\n%s\n' '--- grep for logs replay consumers/tests ---'
rg -n "connectLogEvents|subscribeToLogs|slice\\(0, 25\\)|slice\\(-25\\)|logSubscribers|logs-preview|logs-panel" web-src/mock-backend web-src/axinite -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' || true

Repository: leynos/axinite

Length of output: 4931


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- web-src/mock-backend/src/state.ts ---'
sed -n '180,260p' web-src/mock-backend/src/state.ts

printf '\n%s\n' '--- inspect any mock-backend tests for log ordering ---'
fd -a -e ts -e tsx -e test -e spec web-src/mock-backend web-src/axinite | rg 'log|state|sse|event' || true

Repository: leynos/axinite

Length of output: 2921


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- web-src/axinite/tests/logs-preview.behaviour.test.tsx ---'
sed -n '1,260p' web-src/axinite/tests/logs-preview.behaviour.test.tsx

printf '\n%s\n' '--- web-src/axinite/tests/api-contract-alignment.test.ts ---'
sed -n '1,220p' web-src/axinite/tests/api-contract-alignment.test.ts

Repository: leynos/axinite

Length of output: 8314


Replay the most recent 25 logs oldest→newest. In web-src/mock-backend/src/state.ts:218-226, this.logs is newest-first, so slice(-25) replays the oldest retained entries in reverse chronological order. LogsPreview appends streamed entries, so send this.logs.slice(0, 25).reverse() instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-src/mock-backend/src/state.ts` around lines 218 - 226, Update
subscribeToLogs to replay the newest 25 entries in chronological
oldest-to-newest order by replacing the current logs slice iteration with the
equivalent of taking the first 25 newest-first entries and reversing them before
sending. Preserve subscriber registration and unsubscribe behavior.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review continued from previous batch...

Comment on lines +30 to +39
- name: Checkout repository
uses: actions/checkout@v6
- name: Install Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install uv
# `semantic` and `verify:full` fetch semgrep and moz-fluent-linter
# through uvx.
uses: astral-sh/setup-uv@v5

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== .github/workflows/frontend.yml ==="
nl -ba .github/workflows/frontend.yml | sed -n '1,120p'

echo
echo "=== action refs in workflow ==="
python3 - <<'PY'
from pathlib import Path
import re
p = Path('.github/workflows/frontend.yml')
text = p.read_text()
for m in re.finditer(r'uses:\s*([^\s#]+)', text):
    print(m.group(1))
PY

Repository: leynos/axinite

Length of output: 231


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== .github/workflows/frontend.yml ==="
cat -n .github/workflows/frontend.yml | sed -n '1,140p'

echo
echo "=== all workflow action refs ==="
rg -n 'uses:\s*' .github/workflows -g '*.yml' -g '*.yaml'

Repository: leynos/axinite

Length of output: 12221


Pin the workflow actions and drop checkout credentials. Replace actions/checkout@v6, oven-sh/setup-bun@v2, and astral-sh/setup-uv@v5 with commit SHAs, and set persist-credentials: false on actions/checkout; the checkout token should not remain available to later repository scripts.

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 30-31: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 31-31: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 33-33: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 39-39: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/frontend.yml around lines 30 - 39, Update the workflow
steps for Checkout repository, Install Bun, and Install uv to reference
immutable commit SHAs instead of version tags. Add persist-credentials: false to
the actions/checkout configuration so its token is unavailable to later scripts,
while preserving the existing Bun and uv version settings.

Source: Linters/SAST tools

Comment thread Makefile
Comment on lines +107 to +108
frontend-stub:
cd $(FRONTEND_DIR) && $(BUN) run dev

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Install workspace dependencies before starting the stub runtime.

Make frontend-stub depend on frontend-install; a fresh checkout otherwise
fails before Bun can resolve the development scripts.

Proposed fix
-frontend-stub:
+frontend-stub: frontend-install
 	cd $(FRONTEND_DIR) && $(BUN) run dev
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Makefile` around lines 107 - 108, Update the frontend-stub Make target to
depend on frontend-install so workspace dependencies are installed before
running the Bun development command; preserve the existing runtime command
unchanged.

Comment thread tests/e2e/helpers.py
Comment on lines +139 to +147
async def goto_route(page, name: str, path: str, *, timeout: int = 5000):
"""Click a shell nav link and wait for its route landmark to appear."""
# role=link named after the localized route label (en-GB default).
await page.get_by_role("link", name=name, exact=True).click()
await page.wait_for_url(f"**/{path}", timeout=timeout)
await page.locator(ROUTE_LANDMARK[path]).first.wait_for(
state="visible", timeout=timeout
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Give goto_route a full numpydoc, not a one-liner.

goto_route is imported by test_connection.py, test_extensions.py, and test_skills.py — it is the shared public navigation contract for the whole suite, not a private helper. A one-line summary undersells that; document page, name, path, and timeout, and note the URL-suffix matching behaviour of wait_for_url.

As per path instructions, "Docstrings must follow the numpy style guide. Use a single-line summary for private functions and methods, and full structured docs for all public interfaces."

📝 Proposed docstring
 async def goto_route(page, name: str, path: str, *, timeout: int = 5000):
-    """Click a shell nav link and wait for its route landmark to appear."""
+    """Click a shell nav link and wait for its route landmark to appear.
+
+    Parameters
+    ----------
+    page : playwright.async_api.Page
+        The active Playwright page, already authenticated.
+    name : str
+        Accessible name of the nav link (en-GB route label).
+    path : str
+        URL path segment; must be a key in ``ROUTE_LANDMARK``.
+    timeout : int, optional
+        Timeout in milliseconds for both the URL change and the
+        landmark visibility wait, by default 5000.
+    """
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def goto_route(page, name: str, path: str, *, timeout: int = 5000):
"""Click a shell nav link and wait for its route landmark to appear."""
# role=link named after the localized route label (en-GB default).
await page.get_by_role("link", name=name, exact=True).click()
await page.wait_for_url(f"**/{path}", timeout=timeout)
await page.locator(ROUTE_LANDMARK[path]).first.wait_for(
state="visible", timeout=timeout
)
async def goto_route(page, name: str, path: str, *, timeout: int = 5000):
"""Click a shell nav link and wait for its route landmark to appear.
Parameters
----------
page : playwright.async_api.Page
The active Playwright page, already authenticated.
name : str
Accessible name of the nav link (en-GB route label).
path : str
URL path segment; must be a key in ``ROUTE_LANDMARK``.
timeout : int, optional
Timeout in milliseconds for both the URL change and the
landmark visibility wait, by default 5000.
"""
# role=link named after the localized route label (en-GB default).
await page.get_by_role("link", name=name, exact=True).click()
await page.wait_for_url(f"**/{path}", timeout=timeout)
await page.locator(ROUTE_LANDMARK[path]).first.wait_for(
state="visible", timeout=timeout
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/helpers.py` around lines 139 - 147, Expand the docstring for the
public async function goto_route into full numpydoc format. Document the page,
name, path, and timeout parameters, and describe that wait_for_url matches the
URL suffix pattern before waiting for the route landmark; leave the navigation
behavior unchanged.

Source: Path instructions

@@ -1,76 +1,196 @@
"""Scenario 2: Chat message round-trip via SSE streaming."""
"""Scenario 2: Chat round-trip against the mock LLM (SolidJS chat surface).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract one shared send_chat_message helper into helpers.py. The "fill the Message composer, click Send" sequence is copy-pasted three times, with test_sse_reconnect.py's inline copy even missing the visibility wait the other two have.

  • tests/e2e/scenarios/test_chat.py#L21-26: delete the local _send and call the shared helpers.send_chat_message(page, text) instead.
  • tests/e2e/scenarios/test_html_injection.py#L15-19: delete the local _send and call the same shared helper.
  • tests/e2e/scenarios/test_sse_reconnect.py#L38-40: replace the inlined three-line sequence with the shared helper so it also gets the visibility wait.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/scenarios/test_chat.py` at line 1, Extract the duplicated
message-composer interaction into a shared send_chat_message helper in
helpers.py, including the visibility wait. Remove the local _send helpers from
test_chat.py and test_html_injection.py, and replace their calls with
helpers.send_chat_message(page, text); also replace the inline sequence in
test_sse_reconnect.py with the shared helper.

Comment on lines +29 to +49
async def _assistant_markdown_contains(page, needle: str, *, timeout: int = 60000):
# Generous timeout: the daemon's first LLM round-trip after startup is cold
# (thread setup, pipeline warmup) and can take tens of seconds.
# Inline the needle (JSON-escaped) rather than passing `arg=`; the latter is
# unreliable with this Playwright build.
needle_js = json.dumps(needle)
try:
await page.wait_for_function(
f"""() => [...document.querySelectorAll(
"[data-role='assistant'] .chat-preview__markdown"
)].some((el) => (el.textContent || '').includes({needle_js}))""",
timeout=timeout,
)
except Exception:
texts = await page.eval_on_selector_all(
"[data-role='assistant'] .chat-preview__markdown",
"els => els.map(e => e.textContent)",
)
raise AssertionError(
f"No assistant markdown contained {needle!r}. Seen: {texts!r}"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Catch the specific Playwright timeout, and chain the re-raise.

Catching bare Exception then raising a fresh AssertionError throws away the original traceback — CI failures will show only "No assistant markdown contained ..." with no hint that it was a Playwright timeout underneath. Catch playwright.async_api.TimeoutError specifically and chain with from err.

🔧 Proposed fix
+from playwright.async_api import TimeoutError as PlaywrightTimeoutError
+
 async def _assistant_markdown_contains(page, needle: str, *, timeout: int = 60000):
     ...
     try:
         await page.wait_for_function(
             ...
         )
-    except Exception:
+    except PlaywrightTimeoutError as err:
         texts = await page.eval_on_selector_all(
             "[data-role='assistant'] .chat-preview__markdown",
             "els => els.map(e => e.textContent)",
         )
         raise AssertionError(
             f"No assistant markdown contained {needle!r}. Seen: {texts!r}"
-        )
+        ) from err
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def _assistant_markdown_contains(page, needle: str, *, timeout: int = 60000):
# Generous timeout: the daemon's first LLM round-trip after startup is cold
# (thread setup, pipeline warmup) and can take tens of seconds.
# Inline the needle (JSON-escaped) rather than passing `arg=`; the latter is
# unreliable with this Playwright build.
needle_js = json.dumps(needle)
try:
await page.wait_for_function(
f"""() => [...document.querySelectorAll(
"[data-role='assistant'] .chat-preview__markdown"
)].some((el) => (el.textContent || '').includes({needle_js}))""",
timeout=timeout,
)
except Exception:
texts = await page.eval_on_selector_all(
"[data-role='assistant'] .chat-preview__markdown",
"els => els.map(e => e.textContent)",
)
raise AssertionError(
f"No assistant markdown contained {needle!r}. Seen: {texts!r}"
)
from playwright.async_api import TimeoutError as PlaywrightTimeoutError
async def _assistant_markdown_contains(page, needle: str, *, timeout: int = 60000):
# Generous timeout: the daemon's first LLM round-trip after startup is cold
# (thread setup, pipeline warmup) and can take tens of seconds.
# Inline the needle (JSON-escaped) rather than passing `arg=`; the latter is
# unreliable with this Playwright build.
needle_js = json.dumps(needle)
try:
await page.wait_for_function(
f"""() => [...document.querySelectorAll(
"[data-role='assistant'] .chat-preview__markdown"
)].some((el) => (el.textContent || '').includes({needle_js}))""",
timeout=timeout,
)
except PlaywrightTimeoutError as err:
texts = await page.eval_on_selector_all(
"[data-role='assistant'] .chat-preview__markdown",
"els => els.map(e => e.textContent)",
)
raise AssertionError(
f"No assistant markdown contained {needle!r}. Seen: {texts!r}"
) from err
🧰 Tools
🪛 ast-grep (0.44.1)

[info] 33-33: use jsonify instead of json.dumps for JSON output
Context: json.dumps(needle)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🪛 Ruff (0.15.21)

[warning] 29-29: Missing return type annotation for private function _assistant_markdown_contains

Add return type annotation: None

(ANN202)


[warning] 42-42: Do not catch blind exception: Exception

(BLE001)


[warning] 47-49: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


[warning] 47-49: Avoid specifying long messages outside the exception class

(TRY003)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/scenarios/test_chat.py` around lines 29 - 49, Update
_assistant_markdown_contains to catch playwright.async_api.TimeoutError
specifically instead of bare Exception, and chain the raised AssertionError from
the caught error using “from err” while preserving the existing diagnostic text
and fallback inspection.

Source: Linters/SAST tools

Comment on lines +3 to +19
This document describes the df12 Productions v2a front-end stack for the
Wildside and Corbusier mockups in two layers:

- the target stack for the mockup as it moves from static prototype markup to an
application runtime, and
- the fuller v2a application stack described elsewhere in this repository’s
design and architecture documents.

That distinction matters because the prototype already exercises much of the UI,
styling, routing, localization, and map stack, while the broader product
architecture adds local-first data and orchestration tooling that can sit on top
of the UI layer without changing the rendering model.

The current repository still ships a static prototype under `axinite/`. Treat
the file paths and module names below as the intended SPA layout for the
SolidJS + Kobalte implementation, not as a claim that the static prototype has
already been migrated.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Replace stale pre-migration documentation across the frontend guides.

Align both documents with the implemented Axinite SolidJS SPA instead of
describing unrelated or future prototype work.

  • web-src/docs/v2a-front-end-stack.md#L3-L19: remove the df12 Productions,
    Wildside, Corbusier, and static-prototype framing.
  • web-src/docs/solidjs-tailwind-with-bun.md#L3-L5: describe the current SPA
    implementation rather than an eventual SPA path.

Triage: [type:docstyle]

📍 Affects 2 files
  • web-src/docs/v2a-front-end-stack.md#L3-L19 (this comment)
  • web-src/docs/solidjs-tailwind-with-bun.md#L3-L5
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-src/docs/v2a-front-end-stack.md` around lines 3 - 19, Update
web-src/docs/v2a-front-end-stack.md lines 3-19 to document the implemented
Axinite SolidJS SPA, removing df12 Productions, Wildside, Corbusier, and
static-prototype framing. Also update web-src/docs/solidjs-tailwind-with-bun.md
lines 3-5 to describe the current SPA implementation rather than a future
migration path.

Comment on lines +2 to +7
import { globSync, readFileSync } from "node:fs";
import path from "node:path";

const MAX_CLASS_TOKENS = 12;
const root = process.cwd();
const files = globSync("axinite/src/**/*.tsx", { cwd: root });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check the Bun engine constraint actually declared for this workspace.
cat web-src/package.json | grep -A2 '"engines"'
fd -p 'web-src/(bun.lockb|\.bun-version|\.tool-versions)$'

Repository: leynos/axinite

Length of output: 214


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n-- web-src/scripts/check-classlist-length.ts --\n'
sed -n '1,140p' web-src/scripts/check-classlist-length.ts

printf '\n-- web-src/scripts/find-near-duplicate-classes.ts --\n'
sed -n '1,140p' web-src/scripts/find-near-duplicate-classes.ts

printf '\n-- web-src/package.json --\n'
sed -n '1,220p' web-src/package.json

Repository: leynos/axinite

Length of output: 5381


🌐 Web query:

Bun fs.globSync compatibility added version 1.2.2 changelog

💡 Result:

Bun first introduced compatibility for the fs.globSync function (along with fs.glob and fs.promises.glob) in Bun version 1.2.2, released on February 1, 2025 [1][2][3]. This feature was added to improve Node.js compatibility within the node:fs module [1][4]. When initially introduced, the implementation supported basic functionality, including single glob patterns, but had limitations such as requiring the options argument to be provided [1][5]. Subsequent updates have further refined the fs.globSync API: - In Bun 1.2.17, the options argument was made optional, aligning with Node.js behavior [5]. - In Bun 1.2.18, the behavior was updated so that fs.globSync matches directories by default (previously it only matched files unless configured otherwise) [6][7].

Citations:


Bump the Bun floor or drop fs.globSync from these scripts. web-src/package.json currently permits Bun >=1.1.41, which predates node:fs glob support, so lint:classlist and lint:class-duplicates can fail at start-up instead of running.

📍 Affects 2 files
  • web-src/scripts/check-classlist-length.ts#L2-L7 (this comment)
  • web-src/scripts/find-near-duplicate-classes.ts#L2-L6
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-src/scripts/check-classlist-length.ts` around lines 2 - 7, Resolve the
Bun compatibility mismatch by either raising the minimum Bun version in
web-src/package.json to one that supports node:fs globSync, or replacing
globSync usage with a compatible alternative. Apply the chosen fix to
web-src/scripts/check-classlist-length.ts and
web-src/scripts/find-near-duplicate-classes.ts so both lint scripts start
successfully under the supported runtime.

Comment on lines +6 to +23
const root = process.cwd();
const files = globSync("axinite/src/**/*.tsx", { cwd: root });
const failures: string[] = [];

for (const relativePath of files) {
const absolutePath = path.join(root, relativePath);
const source = readFileSync(absolutePath, "utf8");

for (const match of source.matchAll(/class="([^"\n]+)"/g)) {
const value = match[1] ?? "";
const tokens = value.trim().split(/\s+/u).filter(Boolean);

if (tokens.length > MAX_CLASS_TOKENS) {
failures.push(
`${relativePath}: class attribute contains ${tokens.length} tokens`
);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated glob/regex class-scanning logic into one shared helper.

Both scripts independently glob axinite/src/**/*.tsx, read each file, and regex-match class="..." attributes, but with two different regexes that behave inconsistently (check-classlist-length.ts's pattern excludes newlines and requires a non-empty match; find-near-duplicate-classes.ts's tolerates surrounding whitespace and empty matches). The newline exclusion in particular means a class attribute wrapped across multiple lines silently bypasses the token-count check entirely.

  • web-src/scripts/check-classlist-length.ts#L6-L23: replace the inline glob+regex loop with a shared helper (e.g. scripts/lib/scan-class-attributes.ts) exporting a single regex that tolerates whitespace/newlines around class=.
  • web-src/scripts/find-near-duplicate-classes.ts#L5-L21: import the same shared helper so both checks see identical class="..." extraction, closing the newline gap for both.
📍 Affects 2 files
  • web-src/scripts/check-classlist-length.ts#L6-L23 (this comment)
  • web-src/scripts/find-near-duplicate-classes.ts#L5-L21
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-src/scripts/check-classlist-length.ts` around lines 6 - 23, Extract the
shared TSX class-attribute scanning logic into a helper such as
scan-class-attributes.ts, including the glob, file reading, and one regex that
supports surrounding whitespace, newlines, and empty class values. Update
check-classlist-length.ts and find-near-duplicate-classes.ts to import and use
this helper; apply the change at web-src/scripts/check-classlist-length.ts lines
6-23 and web-src/scripts/find-near-duplicate-classes.ts lines 5-21 so both
checks process identical class attributes.

Comment thread web-src/scripts/dev.ts
Comment on lines +3 to +4
const apiPort = Number(process.env.MOCK_API_PORT ?? "8787");
const defaultPreviewPort = Number(process.env.PREVIEW_PORT ?? "2020");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate port env vars before use.

Number(process.env.MOCK_API_PORT ?? "8787") and the preview-port equivalent silently produce NaN for a malformed value, which then propagates into child env vars as the literal string "NaN" instead of failing fast with a clear message.

🔢 Proposed fix to fail fast on invalid port values
-const apiPort = Number(process.env.MOCK_API_PORT ?? "8787");
-const defaultPreviewPort = Number(process.env.PREVIEW_PORT ?? "2020");
+function parsePort(name: string, fallback: string): number {
+  const value = Number(process.env[name] ?? fallback);
+  if (!Number.isInteger(value) || value <= 0) {
+    throw new Error(`[dev] ${name} must be a positive integer, got "${process.env[name]}"`);
+  }
+  return value;
+}
+
+const apiPort = parsePort("MOCK_API_PORT", "8787");
+const defaultPreviewPort = parsePort("PREVIEW_PORT", "2020");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const apiPort = Number(process.env.MOCK_API_PORT ?? "8787");
const defaultPreviewPort = Number(process.env.PREVIEW_PORT ?? "2020");
function parsePort(name: string, fallback: string): number {
const value = Number(process.env[name] ?? fallback);
if (!Number.isInteger(value) || value <= 0) {
throw new Error(`[dev] ${name} must be a positive integer, got "${process.env[name]}"`);
}
return value;
}
const apiPort = parsePort("MOCK_API_PORT", "8787");
const defaultPreviewPort = parsePort("PREVIEW_PORT", "2020");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-src/scripts/dev.ts` around lines 3 - 4, Validate the values assigned to
apiPort and defaultPreviewPort before they are used, rejecting non-numeric or
otherwise invalid port environment values with a clear failure message. Preserve
the existing defaults when variables are unset, and ensure invalid input fails
fast rather than propagating NaN to child environment variables.

Comment thread web-src/scripts/dev.ts
Comment on lines +129 to +163
let shuttingDown = false;

const stopAll = async (signal: string) => {
if (shuttingDown) {
return;
}
shuttingDown = true;
console.log(`[dev] received ${signal}, shutting down child processes`);
for (const child of managed) {
child.process.kill();
}
await Promise.allSettled(managed.map((child) => child.process.exited));
process.exit(0);
};

process.on("SIGINT", () => {
void stopAll("SIGINT");
});
process.on("SIGTERM", () => {
void stopAll("SIGTERM");
});

const results = await Promise.race(
managed.map(async (child) => ({
label: child.label,
exitCode: await child.process.exited,
}))
);

if (!shuttingDown) {
console.error(
`[dev] ${results.label} exited unexpectedly with code ${results.exitCode}`
);
await stopAll("child-exit");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Unexpected child exit is masked as success.

stopAll always calls process.exit(0), even when reached from the "child exited unexpectedly" branch (line 162) after logging a failure. A crashed child (build/mock-api/preview) therefore still yields a zero exit code from dev.ts, hiding the failure from anything checking the process exit status.

🛑 Proposed fix to propagate a non-zero exit code on unexpected exit
-  const stopAll = async (signal: string) => {
+  const stopAll = async (signal: string, exitCode = 0) => {
     if (shuttingDown) {
       return;
     }
     shuttingDown = true;
     console.log(`[dev] received ${signal}, shutting down child processes`);
     for (const child of managed) {
       child.process.kill();
     }
     await Promise.allSettled(managed.map((child) => child.process.exited));
-    process.exit(0);
+    process.exit(exitCode);
   };
   if (!shuttingDown) {
     console.error(
       `[dev] ${results.label} exited unexpectedly with code ${results.exitCode}`
     );
-    await stopAll("child-exit");
+    await stopAll("child-exit", 1);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let shuttingDown = false;
const stopAll = async (signal: string) => {
if (shuttingDown) {
return;
}
shuttingDown = true;
console.log(`[dev] received ${signal}, shutting down child processes`);
for (const child of managed) {
child.process.kill();
}
await Promise.allSettled(managed.map((child) => child.process.exited));
process.exit(0);
};
process.on("SIGINT", () => {
void stopAll("SIGINT");
});
process.on("SIGTERM", () => {
void stopAll("SIGTERM");
});
const results = await Promise.race(
managed.map(async (child) => ({
label: child.label,
exitCode: await child.process.exited,
}))
);
if (!shuttingDown) {
console.error(
`[dev] ${results.label} exited unexpectedly with code ${results.exitCode}`
);
await stopAll("child-exit");
}
let shuttingDown = false;
const stopAll = async (signal: string, exitCode = 0) => {
if (shuttingDown) {
return;
}
shuttingDown = true;
console.log(`[dev] received ${signal}, shutting down child processes`);
for (const child of managed) {
child.process.kill();
}
await Promise.allSettled(managed.map((child) => child.process.exited));
process.exit(exitCode);
};
process.on("SIGINT", () => {
void stopAll("SIGINT");
});
process.on("SIGTERM", () => {
void stopAll("SIGTERM");
});
const results = await Promise.race(
managed.map(async (child) => ({
label: child.label,
exitCode: await child.process.exited,
}))
);
if (!shuttingDown) {
console.error(
`[dev] ${results.label} exited unexpectedly with code ${results.exitCode}`
);
await stopAll("child-exit", 1);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web-src/scripts/dev.ts` around lines 129 - 163, Update stopAll to accept an
exit code, preserving 0 for SIGINT and SIGTERM while using a non-zero code when
called from the unexpected child-exit branch after logging the failure. Pass the
appropriate code at each call site so child crashes propagate failure through
dev.ts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: medium Business logic, config, or moderate-risk modules Roadmap scope: channel/wasm WASM channel runtime scope: channel/web Web gateway channel scope: ci CI/CD workflows scope: db/libsql libSQL / Turso backend scope: db/postgres PostgreSQL backend scope: docs Documentation size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants