Dashboard frontend modernization: Rspack build, real Tailwind v4, LCM TSX#50
Dashboard frontend modernization: Rspack build, real Tailwind v4, LCM TSX#50ScriptedAlchemy wants to merge 25 commits into
Conversation
Replace the esbuild + hand-rolled-utility dashboard build with the Rspack engine (the one Rsbuild wraps) and real Tailwind v4, while preserving the prod-embed contract exactly: dist files are still embedded at compile time by src/dashboard/assets.rs and served with no Node/Rspack dependency at launch. Rspack runs only at build time. Build (dashboard/build.mjs, replaces esbuild): - shell + holographic/graph/savings plugins built with @rspack/core; React externalized onto the host SDK via resolve.alias to the in-tree shims, so every plugin still shares the host's single React instance and runs unmodified in both the standalone shell and Hermes. - single-file IIFE per plugin at the exact dist paths assets.rs expects. - hermes-wrapper concatenation + lcm copy preserved. - esbuild builder kept as build-esbuild.mjs (`npm run build:esbuild`) fallback. - holographic CSS compiled with real Tailwind v4 (@tailwindcss/node + @tailwindcss/oxide); @layer theme/base stripped so the plugin never clobbers host :root vars, wrapped in @layer hermes-plugin. Holographic styles (dashboard/holographic/src/styles.css): - @import "tailwindcss" + @theme color tokens replace the ~390-line hand-rolled utility subset (the fragile [class*="xl:grid-cols-[..."] attribute selectors). Arbitrary values now resolve natively. - surviving :root provides only the tokens the host doesn't (text-primary/secondary/tertiary, midground, shadow-*); hv-*/ts-card component polish kept verbatim. cn (dashboard/lib/cn.ts): one canonical class-name joiner (flatten nested arrays, keep non-empty strings) consumed by the shell SDK, lib/sdk.ts, and holographic's self-contained in-tree copy. Pinned by test/shashell-sdk.test.mjs. Verified: 100/100 node + 12/12 vitest; jsdom smokes (shell renders + exposes SDK; all 3 plugins register); cargo embed + dashboard serve (all routes 200, byte sizes match); real-browser Playwright smoke (desktop + narrow) exercises Holographic/Similarity/Curation/Code Graph/LCM.
…uild dev server LCM plugin — port the 2020-line hand-written vanilla IIFE (lcm/src/index.js, React.createElement via h()) to modern TSX built as a standard plugin bundle, mirroring graph/savings. Split into navigable modules: entry.tsx (registers "hermes-lcm"), App.tsx, components.tsx, markdown.tsx, helpers.ts. All behavior + hermes-lcm-* class names preserved (style.css unchanged, renamed to styles.css for build uniformity). Built as a TSX plugin (React externalized via lib/ shims) instead of copied verbatim; bundle shrinks 86 KB → 48 KB. Build pipeline — esbuild is gone from the build. build.mjs: - LCM now built with buildPlugin (was copyLcm). - Tailwind CSS minify switched from esbuild.transform to a small CSS compactor (preserves @supports color-mix blocks lightningcss would strip). - esbuild fallback builder (build-esbuild.mjs) and the build:esbuild script removed. esbuild remains a devDependency ONLY for the unit-test bundler helper (test/helpers/module-loader.mjs); not in the shipped build path. Dev server — new dashboard/dev/ Rsbuild dev server (`npm run dev`) with HMR, proxying /api/* to a running `tracedecay dashboard` (TRACEDECAY_DEV_API, default 127.0.0.1:7341; port TRACEDECAY_DEV_PORT, default 7342). The dev entry builds the SDK on window before importing plugin entries, so SDK consumers behave like prod. @rsbuild/plugin-tailwindcss compiles holographic's Tailwind v4 in dev (closes the prior dev/prod styling divergence). Verified: 100/100 node + 12/12 vitest; build emits all 14 artifacts; cargo embed + dashboard serve (all routes 200); real-browser Playwright smoke (desktop) exercises Holographic/Similarity/Curation/Code Graph/LCM incl. the new TSX LCM bundle.
- HolographicMemoryPage: CoverageGauge now exposes role="img" + an
aria-label ("N% HRR coverage, <status>") so the gauge is announced to
assistive tech instead of being sight-only. VIEW_TABS hoisted to module
scope (it was rebuilt on every render; it references only module-scope
icons).
- SavingsExplorer: the Savings/Sessions/Models view switch is now a proper
tablist (role="tablist" + role="tab" + aria-selected), matching the shell's
tablist pattern for screen-reader parity.
dev server (dashboard/dev/run.mjs): both Rsbuild Tailwind-v4 integrations (@rsbuild/plugin-tailwindcss and @tailwindcss/postcss via tools.postcss) segfault natively in this execution environment (createRsbuild core dump; @rspack/core itself is fine, so the Tailwind native path is the trigger). Ship the dev server as pluginReact()-only so `npm run dev` actually works — HMR + every non-holographic plugin styled. Holographic renders unstyled in dev (documented divergence); the prod build remains the source of truth for its Tailwind v4 styles. Verified: dev server starts (no segfault), serves HTTP 200, /api proxy wired. docs/dashboard.md: updated the build/frontend/dev sections to the new reality — Rspack build (@rspack/core, per-plugin IIFE, React externalized via shims), real Tailwind v4 for holographic, LCM as a TSX bundle, the `npm run dev` Rsbuild HMR server (env vars TRACEDECAY_DEV_API / TRACEDECAY_DEV_PORT, /api proxy), and the unchanged prod include_bytes! embed contract.
Add lib/primitives.tsx (+ lib/primitives.css) with shared EmptyState, ErrorPanel, SkeletonLines, Stat, BarList — the small UI patterns every plugin hand-rolled under a different class namespace. Components build on the SDK primitives + a tdp-* namespace whose colors resolve through host --color-* vars, so they theme correctly in both the standalone shell and Hermes. CSS is delivered by build.mjs: a new buildPlugin `primitives` option prepends lib/primitives.css to the consuming plugin's dist stylesheet (rides into standalone serve + the Hermes-wrapper concat). Adopted in graph (CodeGraphExplorer) — inline tsg-empty / tsg-error replaced with EmptyState / ErrorPanel (visible text + behavior preserved; graph's other tsg-* styling untouched) — as the reference pattern. Holographic stays self-contained; savings/lcm adoption is follow-up. Verified: 100/100 node + 12/12 vitest; build emits all artifacts; real- browser Playwright smoke (desktop+narrow) passes with the primitives prepended into graph's served CSS.
…ic tokens Replace the ~120-line block of 20 per-component [data-theme="light"] overrides with 32 semantic tokens (--ts-button-bg-2, --ts-card-bg, --ts-input-bg, --ts-tab-active-bg, ...) defined once in :root (dark) and flipped in :root[data-theme="light"]. Component rules now reference the tokens and theme automatically, so new components no longer need a matching manual light override. Dark and light computed values are byte-identical to before (every token's dark value = the old dark rule value; every light value = the old override value). Three rules with no original light override were intentionally left hardcoded (tokenizing them would have changed light-theme output). Residual per-component [data-theme="light"] selectors: 0. Verified: real-browser Playwright smoke (desktop+narrow) passes.
|
Route tracedecay info/todos reads and symbol-edit writes through ProjectPath resolution so out-of-root paths are rejected while valid files still populate touched context. Also update dashboard asset wording to describe UTF-8 JavaScript output independent of bundler.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 64a0025aa5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| <Stat value={fmtInt(c.summary_node_count)} label="summaries" /> | ||
| <Stat value={fmtInt(c.token_estimate_total)} label="msg tokens" /> | ||
| <Stat | ||
| value={ratioStr(c.source_token_count, c.summary_token_count)} |
There was a problem hiding this comment.
Import ratioStr before rendering sessions
Opening any LCM session now renders SessionDetail, but this component calls ratioStr without importing it from helpers.ts. Because Rspack/SWC does not type-check this as a module import error, the first session drawer render hits a ReferenceError and crashes the LCM tab instead of showing the session details.
Useful? React with 👍 / 👎.
| await Promise.all( | ||
| PLUGIN_ENTRIES.map(async (p) => { | ||
| try { | ||
| await import(/* @vite-ignore */ p.spec); |
There was a problem hiding this comment.
Make dev plugin imports statically discoverable
With the new Rsbuild/Rspack dev server, this fully dynamic import(p.spec) is not statically analyzable; Rspack's module-methods docs state that fully dynamic imports such as import(foo) are not possible because the bundle must know what files to include (https://rspack.rs/api/runtime-api/module-methods#dynamic-expressions-in-import). The Vite-only @vite-ignore comment does not help here, so npm run dev will build without these plugin entry modules and every plugin registration can fall into the catch path.
Useful? React with 👍 / 👎.
Rsbuild is now the single build tool. build.mjs is a thin orchestrator over build.shared.mjs, which builds the shell + every plugin via createRsbuild (@rsbuild/core + pluginReact), emitting the same single-file IIFE per plugin at the exact dist/ paths (splitChunks/runtimeChunk off, BannerPlugin, React externalized via the in-tree shims). The dev server config is shared there too (createDashboardDevConfig). Holographic Tailwind v4 still compiles via the programmatic @tailwindcss/node + oxide path (strip @layer theme/base, wrap @layer hermes-plugin), not the segfault-prone Rsbuild tailwind plugins. esbuild is fully removed: the unit-test bundler (test/helpers/module-loader.mjs) now bundles with @rspack/core to an ESM temp module (100/100 node tests still pass, ~unchanged runtime), and esbuild is dropped from devDependencies (no longer in node_modules at all). Redundant build-rsbuild.mjs/rsbuild.config.ts alternatives removed. Added dashboard/tsconfig.json + `npm run typecheck` (tsc --noEmit) with typescript as a devDep. The typecheck is intentionally lenient (strict:false) to surface real bugs over intentional host-SDK `any` noise — it already caught two real undefined-ref crashes (fixed in the follow-up commit). Verified: Rsbuild build emits all 14 artifacts; 100/100 node + 12/12 vitest; real-browser Playwright smoke passes against the cargo-embedded Rsbuild binary.
Real bugs surfaced by `npm run typecheck` (undefined at runtime): - lcm/src/components.tsx used ratioStr (compression ratio) without importing it — crash when the compression view rendered. Now imported from ./helpers. - holographic/src/CurationPanel.tsx referenced loadStatus (Status-tab refresh) without destructuring it from useCurationData — dead refresh button. Now destructured. Shared-primitives adoption (lib/primitives): savings (ErrorPanel, Stat) and lcm (EmptyState, ErrorPanel) adopt the shared components; graph's OverviewPanel adopts BarList/EmptyState (dropping its hand-rolled HBarChart). Holographic stays self-contained. (graph/savings/lcm build with primitives:true so lib/primitives.css is prepended to their dist stylesheet.) a11y: holographic Stat now exposes its hint via aria-describedby + a visually-hidden description element (native title kept for sighted users). docs/dashboard.md: documents the shared primitives + buildPlugin primitives opt-in, the canonical lib/cn.ts, and the dev-server Rsbuild-Tailwind segfault limitation (dev is pluginReact()-only; prod is the source of truth for holographic Tailwind styles). Verified: 100/100 node + 12/12 vitest; Rsbuild build clean; real-browser Playwright smoke passes.
Drop holographic jsx/react shims so Rsbuild typecheck covers all panels; rebuild embedded dist when dashboard sources drift and fix LCM fetchSession typing.
Type-checking is now integrated into the Rsbuild build/dev via @rsbuild/plugin-type-check (ts-checker-rspack-plugin), so the separate 'tsc --noEmit' npm script is redundant. The build is the source of truth for type errors.
Modernizes the dashboard frontend build and UI onto a single Rust-based toolchain (Rspack/Rsbuild) and cleans up the inconsistencies called out in a frontend review. Prod-embed contract is unchanged: `src/dashboard/assets.rs` still `include_bytes!`/`include_str!` the `dist/` files at compile time, so the shipped binary serves the UI with no Node/Rspack dependency at launch — Rspack runs only at build time.
Build (Rspack, no esbuild in the pipeline)
Plugins
Shell / a11y
Dev server
Verification
Not in scope (follow-ups)
Branch based on `profile-storage` so the diff is UI-only. Retarget to `master` if you prefer.