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
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)#275leynos wants to merge 37 commits into
Conversation
There was a problem hiding this comment.
Sorry @leynos, your pull request is larger than the review limit of 150000 diff characters
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
Summary
ValidationFrontend, Rust, end-to-end, formatting, linting, documentation, embedded-asset, and CI workflow checks are reported as passing. Remaining work
WalkthroughThis 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 ChangesRFC 0009 Feature Flags Backend
Embedded SPA and Legacy UI Serving
SolidJS Frontend Application
SolidJS Frontend Tests and Locales
Mock Backend
Python E2E Test Migration
Tooling, CI, and 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
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"
Possibly related PRs
Suggested labels
Poem
Caution Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional.
❌ Failed checks (4 errors, 8 warnings)
✅ Passed checks (8 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
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.
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.
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 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.
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.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Sorry @leynos, your pull request is larger than the review limit of 150000 diff characters
|
@coderabbitai review |
✅ Action performedReview finished.
|
✅ Action performedReview finished.
|
There was a problem hiding this comment.
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 winSplit 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 anytest_*.pyunderscenarios/, 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
⛔ Files ignored due to path filters (3)
src/channels/web/static/solid/assets/axinite32.icois excluded by!**/*.icoweb-src/axinite/assets/icons/axinite32.icois excluded by!**/*.icoweb-src/bun.lockis excluded by!**/*.lock
📒 Files selected for processing (201)
.codescene/code-health-rules.json.github/workflows/frontend.ymlMakefileREADME.mddocs/execplans/adopt-solidjs-ui-followups.mddocs/execplans/adopt-solidjs-ui.mddocs/front-end-architecture.mddocs/rfcs/0009-feature-flags-frontend.mddocs/roadmap.mddocs/solidjs-frontend.mdmigrations/V18__feature_flag_overrides.sqlmigrations/libsql_schema.sqlsrc/bootstrap/tests/migration_support.rssrc/channels/wasm/wrapper/tests/dispatch.rssrc/channels/wasm/wrapper/tests/mod.rssrc/channels/wasm/wrapper/tests/recording_store.rssrc/channels/web/CLAUDE.mdsrc/channels/web/handlers/feature_registry.rssrc/channels/web/handlers/features.rssrc/channels/web/handlers/mod.rssrc/channels/web/handlers/settings.rssrc/channels/web/handlers/settings/tests.rssrc/channels/web/handlers/static_files.rssrc/channels/web/handlers/ui_assets.rssrc/channels/web/mod.rssrc/channels/web/server.rssrc/channels/web/server/tests/fixtures.rssrc/channels/web/static/solid/assets/app.jssrc/channels/web/static/solid/assets/index.csssrc/channels/web/static/solid/index.htmlsrc/channels/web/static/solid/locales/ar/common.ftlsrc/channels/web/static/solid/locales/de/common.ftlsrc/channels/web/static/solid/locales/en-GB/common.ftlsrc/channels/web/static/solid/locales/fr/common.ftlsrc/channels/web/static/solid/locales/hi/common.ftlsrc/channels/web/static/solid/locales/it/common.ftlsrc/channels/web/static/solid/locales/ja/common.ftlsrc/channels/web/static/solid/locales/nl/common.ftlsrc/channels/web/static/solid/locales/pl/common.ftlsrc/channels/web/static/solid/locales/zh-CN/common.ftlsrc/channels/web/test_helpers.rssrc/channels/web/ws/tests.rssrc/db/CLAUDE.mdsrc/db/forwarders.rssrc/db/libsql/settings.rssrc/db/libsql_migrations.rssrc/db/postgres/settings.rssrc/db/settings.rssrc/db/traits/settings.rssrc/history/store/settings.rssrc/reload/config_loader.rssrc/startup/unix_runtime.rssrc/testing/null_db/capturing_store/delegation.rssrc/testing/null_db/null_database/settings_store.rstests/channels/openai_compat/helpers.rstests/channels/openai_compat/validation.rstests/channels/ws_gateway/helpers.rstests/e2e/.gitignoretests/e2e/CLAUDE.mdtests/e2e/conftest.pytests/e2e/helpers.pytests/e2e/mock_llm.pytests/e2e/scenarios/test_chat.pytests/e2e/scenarios/test_connection.pytests/e2e/scenarios/test_extensions.pytests/e2e/scenarios/test_html_injection.pytests/e2e/scenarios/test_skills.pytests/e2e/scenarios/test_sse_reconnect.pytests/e2e/scenarios/test_tool_approval.pytests/trybuild/settings_compat.rstypos.local.tomltypos.tomlweb-src/.gitignoreweb-src/.markdownlint-cli2.jsoncweb-src/AGENTS.mdweb-src/CONTRIBUTING.mdweb-src/Makefileweb-src/axinite/index.htmlweb-src/axinite/public/locales/ar/common.ftlweb-src/axinite/public/locales/de/common.ftlweb-src/axinite/public/locales/en-GB/common.ftlweb-src/axinite/public/locales/fr/common.ftlweb-src/axinite/public/locales/hi/common.ftlweb-src/axinite/public/locales/it/common.ftlweb-src/axinite/public/locales/ja/common.ftlweb-src/axinite/public/locales/nl/common.ftlweb-src/axinite/public/locales/pl/common.ftlweb-src/axinite/public/locales/zh-CN/common.ftlweb-src/axinite/src/app/providers.tsxweb-src/axinite/src/app/router.tsxweb-src/axinite/src/components/app-shell.tsxweb-src/axinite/src/components/auth-gate.tsxweb-src/axinite/src/components/chat-cards.tsxweb-src/axinite/src/components/chat-preview.tsxweb-src/axinite/src/components/debug-flag-panel.tsxweb-src/axinite/src/components/extension-pairing.tsxweb-src/axinite/src/components/extensions-preview.tsxweb-src/axinite/src/components/jobs-preview.tsxweb-src/axinite/src/components/jobs/file-tree.tsxweb-src/axinite/src/components/jobs/format.tsweb-src/axinite/src/components/jobs/job-detail.tsxweb-src/axinite/src/components/locale-picker.tsxweb-src/axinite/src/components/logs-preview.tsxweb-src/axinite/src/components/memory-preview.tsxweb-src/axinite/src/components/restart-control.tsxweb-src/axinite/src/components/route-page.tsxweb-src/axinite/src/components/routines-preview.tsxweb-src/axinite/src/components/skills-preview.tsxweb-src/axinite/src/components/tee-attestation.tsxweb-src/axinite/src/components/wasm-channel-stepper.tsxweb-src/axinite/src/global.d.tsweb-src/axinite/src/lib/api/chat.tsweb-src/axinite/src/lib/api/client.tsweb-src/axinite/src/lib/api/contracts.tsweb-src/axinite/src/lib/api/extensions.tsweb-src/axinite/src/lib/api/gateway.tsweb-src/axinite/src/lib/api/jobs.tsweb-src/axinite/src/lib/api/logs.tsweb-src/axinite/src/lib/api/memory.tsweb-src/axinite/src/lib/api/pairing.tsweb-src/axinite/src/lib/api/routines.tsweb-src/axinite/src/lib/api/skills.tsweb-src/axinite/src/lib/auth/token.tsweb-src/axinite/src/lib/base-path.tsweb-src/axinite/src/lib/connection-status.tsweb-src/axinite/src/lib/feature-flags/registry.tsweb-src/axinite/src/lib/feature-flags/runtime.tsxweb-src/axinite/src/lib/i18n/provider.tsxweb-src/axinite/src/lib/i18n/runtime.tsweb-src/axinite/src/lib/i18n/supported-locales.tsweb-src/axinite/src/lib/markdown.tsweb-src/axinite/src/lib/restart.tsweb-src/axinite/src/lib/route-config.tsweb-src/axinite/src/lib/string-case.tsweb-src/axinite/src/lib/tee.tsweb-src/axinite/src/lib/test-hooks.tsweb-src/axinite/src/main.tsxweb-src/axinite/src/styles/index.cssweb-src/axinite/src/styles/semantic.cssweb-src/axinite/tests/api-contract-alignment.test.tsweb-src/axinite/tests/app-shell.a11y.test.tsxweb-src/axinite/tests/app-shell.behaviour.test.tsxweb-src/axinite/tests/auth-gate.behaviour.test.tsxweb-src/axinite/tests/auth-token.test.tsweb-src/axinite/tests/base-path.test.tsweb-src/axinite/tests/chat-cards.test.tsweb-src/axinite/tests/chat-preview.behaviour.test.tsxweb-src/axinite/tests/e2e/app-shell.pw.tsweb-src/axinite/tests/extension-pairing.behaviour.test.tsxweb-src/axinite/tests/extensions-preview.a11y.test.tsxweb-src/axinite/tests/extensions-preview.behaviour.test.tsxweb-src/axinite/tests/feature-flags.test.tsweb-src/axinite/tests/jobs-preview.behaviour.test.tsxweb-src/axinite/tests/logs-preview.behaviour.test.tsxweb-src/axinite/tests/mock-backend-contract.test.tsweb-src/axinite/tests/mock-backend-streaming-routes.test.tsweb-src/axinite/tests/restart-control.behaviour.test.tsxweb-src/axinite/tests/restart.test.tsweb-src/axinite/tests/setup-vitest-a11y.tsweb-src/axinite/tests/setup-vitest.tsweb-src/axinite/tests/support/i18n-test-runtime.tsweb-src/axinite/tests/support/test-providers.tsxweb-src/axinite/tests/supported-locales.test.tsweb-src/axinite/tests/tee-attestation.behaviour.test.tsxweb-src/axinite/tests/tee.test.tsweb-src/axinite/tests/test-hooks.behaviour.test.tsxweb-src/axinite/tests/wasm-channel-stepper.test.tsweb-src/biome.jsoncweb-src/docs/axinite-v2a-frontend-architecture.mdweb-src/docs/building-accessible-and-responsive-progressive-web-applications.mdweb-src/docs/daisyui-v5-guide.mdweb-src/docs/data-model-driven-card-architecture.mdweb-src/docs/enforcing-semantic-tailwind-best-practice.mdweb-src/docs/execplans/mock-backend.mdweb-src/docs/execplans/solidjs-translation.mdweb-src/docs/high-velocity-accessibility-first-component-testing.mdweb-src/docs/pure-accessible-and-localizable-solidjs-components.mdweb-src/docs/semantic-tailwind-with-daisyui-best-practice.mdweb-src/docs/solidjs-tailwind-with-bun.mdweb-src/docs/tailwind-v3-v4-migration-guide.mdweb-src/docs/tailwind-v4-guide.mdweb-src/docs/v2a-front-end-stack.mdweb-src/mock-backend/src/fixtures.tsweb-src/mock-backend/src/preview-server.tsweb-src/mock-backend/src/server.tsweb-src/mock-backend/src/state-helpers.tsweb-src/mock-backend/src/state.tsweb-src/mock-backend/src/streaming-routes.tsweb-src/package.jsonweb-src/playwright.config.tsweb-src/scripts/check-classlist-length.tsweb-src/scripts/check-fluent-coverage.tsweb-src/scripts/check-fluent-vars.tsweb-src/scripts/dev.tsweb-src/scripts/find-near-duplicate-classes.tsweb-src/tools/semgrep-semantic.ymlweb-src/tools/stylelint.config.cjsweb-src/tsconfig.jsonweb-src/vite.config.tsweb-src/vitest.a11y.config.tsweb-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
| 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). |
There was a problem hiding this comment.
📐 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
| - **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. |
There was a problem hiding this comment.
🗄️ 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
| - [ ] 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. |
There was a problem hiding this comment.
🗄️ 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
| - `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. |
There was a problem hiding this comment.
📐 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.
| Box::pin(async move { | ||
| self.writes | ||
| .lock() | ||
| .expect("settings writes lock poisoned") | ||
| .push(key.to_string()); | ||
| Ok(()) | ||
| }) |
There was a problem hiding this comment.
🩺 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); |
There was a problem hiding this comment.
📐 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 deprecatedclip: rect(0, 0, 0, 0)in.catalogue-table__captionwithclip-path: inset(50%)(keep the rest of the visually-hidden pattern).web-src/axinite/src/styles/semantic.css#L2260-L2260: lowercasecurrentColortocurrentcoloron.shell-restart__icon.web-src/axinite/src/styles/semantic.css#L2377-L2377: lowercasecurrentColortocurrentcoloron.stepper-circle.web-src/axinite/src/styles/semantic.css#L2576-L2576: lowercasecurrentColortocurrentcoloron.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-L2260web-src/axinite/src/styles/semantic.css#L2377-L2377web-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"; | |||
There was a problem hiding this comment.
📐 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 sharedextensionApiMocksmap andvi.mockfactory from a newtests/support/extension-api-mocks.tsinstead of redeclaring them; keep the per-testinstalledExtensions/registryEntriesmutation 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 fixedfirecrawlfixture (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.
| Object.defineProperty(navigator, "clipboard", { | ||
| configurable: true, | ||
| value: { writeText }, | ||
| }); |
There was a problem hiding this comment.
📐 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.
| 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"), | ||
| ]), | ||
| ]; |
There was a problem hiding this comment.
📐 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.
| 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.
| 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); | ||
| }; | ||
| } |
There was a problem hiding this comment.
🎯 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/**' || trueRepository: 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"; }
doneRepository: 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/**' || trueRepository: 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' || trueRepository: 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.tsRepository: 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.
| - 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 |
There was a problem hiding this comment.
🔒 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))
PYRepository: 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
| frontend-stub: | ||
| cd $(FRONTEND_DIR) && $(BUN) run dev |
There was a problem hiding this comment.
🎯 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.
| 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 | ||
| ) | ||
|
|
There was a problem hiding this comment.
📐 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.
| 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). | |||
There was a problem hiding this comment.
📐 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_sendand call the sharedhelpers.send_chat_message(page, text)instead.tests/e2e/scenarios/test_html_injection.py#L15-19: delete the local_sendand 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.
| 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}" | ||
| ) |
There was a problem hiding this comment.
📐 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.
| 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
| 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. |
There was a problem hiding this comment.
📐 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.
| 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 }); |
There was a problem hiding this comment.
🎯 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.jsonRepository: 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:
- 1: https://bun.sh/blog/bun-v1.2.2
- 2: https://bun.com/blog/bun-v1.2.2
- 3: oven-sh/bun@bun-v1.2.1...bun-v1.2.2
- 4: feat(node/fs): add
fs.glob,fs.globSync, andfs.promises.globoven-sh/bun#16676 - 5: https://bun.com/blog/bun-v1.2.17
- 6: https://bun.sh/blog/bun-v1.2.18
- 7: https://bun.com/blog/bun-v1.2.18
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.
| 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` | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
📐 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 aroundclass=.web-src/scripts/find-near-duplicate-classes.ts#L5-L21: import the same shared helper so both checks see identicalclass="..."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.
| const apiPort = Number(process.env.MOCK_API_PORT ?? "8787"); | ||
| const defaultPreviewPort = Number(process.env.PREVIEW_PORT ?? "2020"); |
There was a problem hiding this comment.
🎯 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.
| 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.
| 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"); | ||
| } |
There was a problem hiding this comment.
🩺 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.
| 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.
Summary
This branch replaces the legacy handwritten browser shell with the SolidJS
single-page application from
axinite-mockupas 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 embeddedinto 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 legacyshell 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
docs/solidjs-frontend.md
for the architecture: commands, the stub runtime and its deterministic
fixtures, stubbed HTTP/SSE routes, flag overrides, and serving model.
src/channels/web/handlers/ui_assets.rs
embeds the built SPA and selects the UI variant;
src/channels/web/static/solid/
holds the committed build output, gated for staleness by
make frontend-verify.src/channels/web/handlers/features.rs
(resolution: environment > deployment override > subsystem availability >
compiled default, plus the
X-Axinite-Versionheader),src/channels/web/handlers/feature_registry.rs
(per-deployment cache), and the
feature_flag:interception insrc/channels/web/handlers/settings.rs.
Persistence is a dedicated
feature_flag_overridestable:migrations/V18__feature_flag_overrides.sql
and the libsql incremental in
src/db/libsql_migrations.rs.
web-src/axinite/src/lib/api/
(typed client with bearer/SSE-token auth),
web-src/axinite/src/components/
(chat media and cards, restart control, TEE popover, pairing stepper,
jobs detail tabs, logs route), and
web-src/mock-backend/
(the stub).
web-src/axinite/tests/
(126 unit/behaviour/a11y/contract tests),
tests/e2e/
(all seven scenarios rewritten to the SolidJS DOM via the documented
testability contract, legacy pin removed), and the new CI job in
.github/workflows/frontend.yml.
Validation
make check-fmt,make lint(three clippy feature combinations pluswhitaker): 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 unitand accessibility suites, Fluent checks, semantic-CSS rules, workspace
Playwright spec, moz-fluent-lint): pass.
make frontend-verify(embedded-asset staleness): pass.pytest tests/e2e/ -vagainst the real daemon (libsql build): 35passed, 1 skipped (live-registry skills install self-skip).
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.
limiting).
Notes
notes: the dedicated override table (instead of extending
settings),the optional
X-Deployment-Idon reads (defaulting to"default"),400 responses for
feature_flag:keys via the settings read/deletepaths, and the disable-only subsystem layer.
/v1/-prefixed pathswhile the daemon posts to
{base}/chat/completions; every mock LLM turnhad been silently 404ing. Fixed in
tests/e2e/mock_llm.py.
retrospectives: RFC 0018 Stage 5 (remove the legacy shell and
tests/web_static_app.test.mjsonce the rollback window closes) androadmap task 4.5.7 (the
feature_flags_changedSSE event).