- Call fetchData() after successful submit in handleSubmit to update stats bar and columns
- Add unit tests to verify fetchData is called on success and not on failure
- Fixes B-016: public roadmap stats don't refresh after submit
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Replaces the bespoke <table> in /dashboard/items with <DataTable> (TanStack-
powered sorting + client pagination), keeping the existing badge cells, title
link, labels and delete action via ColumnDef cell renderers. Server-side
type/status/priority/search filters retained (enableFilter=false on the table).
Verified: tsc --noEmit clean; vitest 31/31; next build --webpack succeeds
(/dashboard/items compiles).
RichTextEditor (toolbar + slash menu + async mentions, SSR-safe via
immediatelyRender:false) + RichTextViewer (generateHTML, server-renderable) +
standalone Toolbar. Pure filterSlashItems/filterUsers helpers. 12/12 vitest
(incl. live editor mount + bold toggle in happy-dom); tsc clean; published to
Gitea.
DataTable wrapper: sort, global filter, pagination, row selection + bulk
action bar, column resize/pin/reorder, compact/comfortable density, and a
virtualised mode for 10k+ rows (seeded initialRect for SSR/headless).
Note: roadmap 9.C says 'TanStack Table v9' but no stable v9 exists yet; built
on the current stable v8.21.3 + react-virtual 3.13.
Verified: vitest 10/10; tsc --noEmit clean; tsc build emits dist; published
@bytelyst/data-table@0.1.0 to local Gitea registry.
Adds 13 vitest cases for the Wave 9.D loading primitives; ui suite now 19/19.
Removes the resolved ROADMAP-EXEC-TODO #2 marker from Skeleton.tsx.
Verified: npx vitest run --pool forks (19 passed); npx tsc --noEmit (clean).
@bytelyst/ui 0.2.0 -> 0.2.1
- Combobox: scroll the highlighted option into view during arrow-key nav so
it never leaves the popover viewport (IMP-1)
- TagInput: new commitOnBlur prop (default false) — committing the buffer on
blur surprised users clicking away to discard. BEHAVIOR CHANGE: blur no
longer commits unless commitOnBlur is set (IMP-3)
- Add vitest + happy-dom + @testing-library/react devDeps, test script, and
packages/ui/vitest.config.ts; 6 unit tests for Combobox + TagInput (GAP-4)
- Drop the stale 'vitest pending' ROADMAP-EXEC-TODO comments
6/6 tests pass; tsc clean.
──────────────────────────────────────────────────────────────────
customizable-workspace
──────────────────────────────────────────────────────────────────
Two issues caught in the audit pass:
1. **Corrupt persisted spans broke the grid layout.**
localStorage entries from older versions (or hand-edited debug
sessions) could contain span=NaN / 0 / -3 / 99. These flowed
straight into `grid-column: span <bad>` which silently broke
the whole row. The visual symptom was a tile rendering at zero
width or pushing every sibling off-screen.
Fix: `reconcile()` now clamps every span (including newly
appended tiles' defaultSpan) to the legal `[1, 4]` range via
`sanitiseSpan()`.
2. **Re-reconcile effect could loop when callers forget to memoise.**
The `useEffect([tiles, hydrated])` always called `setLayout`
with a fresh `{ entries: [...] }` object reference, even when
the content was identical. If `tiles` itself was a fresh
reference per parent render (e.g. `tiles=[{...}]` inline),
every render \u2192 setLayout \u2192 save effect \u2192 (no loop because
tiles ref same), but constant unnecessary writes to
localStorage.
Fix: added `sameLayout(a, b)` structural-equality check;
setLayout now short-circuits to the previous state when the
reconciled output is identical.
Tests: 10 \u2192 11
reconcile \u00b7 sanitises corrupt spans (NaN/0/negative/>4) \u2192 clamp
──────────────────────────────────────────────────────────────────
generative-theme
──────────────────────────────────────────────────────────────────
Cosmetic but worth fixing: the `rose` palette regex included
`warm` as a keyword, but the `citrus` palette \u2014 listed earlier in
the PALETTES table \u2014 also matched `warm`. Since first-match wins,
`warm` was unreachable in rose and the entry was misleading.
Dropped `warm` from the rose regex. Citrus retains it (was always
where it routed in practice). All 18 existing tests still pass.
Two latent bugs caught in the audit pass:
1. **AudioContext leak in AudioWaveform.**
The lazy WebAudio decoder constructed an `AudioContext` per
mount-with-audioUrl and never called `.close()`. Browsers cap the
total per page (Chrome ~6, Firefox ~6) so a long-lived session that
remounted waveforms enough times eventually hit
`InvalidStateError: cannot create AudioContext` and silently
stopped decoding.
Fix: wrap `decodeAudioData` in try/finally and `close()` the
context in the finally. Errors from close() are swallowed (best-
effort cleanup).
2. **Undefined img src in ImageGenStream.**
When `status=streaming` arrived before the first
`snapshot.partialUrl` (very common during the SSE handshake),
the component rendered <img src={undefined}> which browsers treat
as a broken-image icon. Same for status=complete + missing
finalUrl.
Fix: compute `visibleSrc` once; only mount <img> if a source
exists, otherwise show 'Waiting for first frame\u2026' placeholder.
Also removed the dead `revealedAt` state \u2014 the prior 0.95/1
opacity dance was imperceptible and contributed nothing.
3. **Bonus: AudioWaveform `bars` clamp.**
`bars={0}` (or negative / fractional) divided by zero on the
canvas slot math and rendered an empty waveform. Now clamped to
`Math.max(1, Math.floor(barsProp))`.
Tests: 10 \u2192 12
AudioWaveform \u00b7 bars=0 doesn't crash + canvas still renders
ImageGenStream \u00b7 streaming without partialUrl shows placeholder
instead of <img src={undefined}>
Audit pass found two latent issues:
1. **NaN/Infinity broke the SVG path.** `LineChart` mapped raw values
through `yScale()` without sanitising, so any non-finite input
emitted a literal 'NaN' in the path `d` attribute and silently
broke the visible stroke for the whole series. Same shape in
`AreaChart`.
2. **`compactNumber()` was duplicated.** Three identical copies
lived in LineChart / BarChart / AreaChart.
Fixes (all in `utils.ts`):
+ `compactNumber(v)` now exported (returns '' for non-finite)
+ `filterFinite(values)` returns `[index, value]` pairs,
keeping the original X-axis spacing for the surviving points
Behavioural changes:
- LineChart `series` containing NaN/Infinity → path skips those
points cleanly. Series of *entirely* non-finite values render
nothing (was: a fully NaN-poisoned path).
- AreaChart `values` containing NaN/Infinity → same.
- BarChart unchanged (was already safe via `extent`).
Tests: 19 \u2192 23 (4 new regression cases)
utils \u00b7 compactNumber k-suffix + non-finite handling
utils \u00b7 filterFinite preserves original indices
LineChart \u00b7 NaN/Infinity never appear in path `d`
LineChart \u00b7 all-non-finite series renders zero <g>
Adds §11 to the v3 cross-repo UX roadmap: a 202-item, machine-parsable
checklist that coding agents flip as they ship work, with the
`learning_ai_uxui_web` showcase as the canonical visual-iteration
surface ahead of any product adoption.
──────────────────────────────────────────────────────────────────
§11 — Showcase-first workflow & live progress tracker
──────────────────────────────────────────────────────────────────
\u00a711.0 The showcase-first rule (non-negotiable 7-step recipe)
1. Scaffold in common_plat/packages/<name>/
2. Vendor snapshot to learning_ai_uxui_web/src/lib/<name>-preview/
3. Showcase route at src/app/showcase/<group>/<slug>/page.tsx
+ catalog entry in src/catalog/routes.ts
4. MSW mock any backend dependency
5. Smoke test (axe + visual-regression baseline)
6. Publish to Gitea registry; delete preview; swap imports
7. Adopt in ≥ 1 product — that PR closes the checklist row
\u00a711.1 Agent update protocol — flip `- [ ]` → `- [x]` inline with
the commit; trail the short-SHA in parentheses; multi-step
rows tracked sub-bullet by sub-bullet.
\u00a711.2 Live "Progress at a glance" block (auto-rewritten by the
counter script below).
\u00a711.3 Wave 8 Rollout — 18 items
\u00a711.4 Wave 9 Data — 42 items
\u00a711.5 Wave 10 Shells — 35 items
\u00a711.6 Wave 11 Adaptive — 26 items
\u00a711.7 Wave 12 Mobile — 26 items
\u00a711.8 Wave 13 Futurism — 39 items
\u00a711.9 Cross-cutting — 8 items
\u00a711.10 Customer-magnet demos — 8 items
───────────────────────────────
TOTAL — 202 items
Every package row carries a paired "**Showcase:**` /showcase/...`"
route entry so agents know exactly where the demo lives. Cross-
referenced with the per-product upgrade matrix in §5.
Renumbered hygiene §11 → §12. Header bumped v3.1 → v3.2.
──────────────────────────────────────────────────────────────────
scripts/count-roadmap-progress.ts
──────────────────────────────────────────────────────────────────
TypeScript node script (tsx) that:
- Parses the v3 doc, counts `- [ ]` and `- [x]` per §11.x section
- Rewrites the §11.2 fenced block in place with live counts +
bar charts + percentages
- Updates the `· \`N / M\`` suffix on every `### 11.x Wave …`
heading so per-wave totals stay accurate
- Idempotent: re-runs are no-ops when nothing changed
- Verified: emits `0 / 202 done` on initial run; "up to date"
on second run
Wired as Wave 8.A.7 (also tracked at CC.8 — should land in pre-commit
hook so the §11.2 block can never drift from reality).
Mirror in copilot/learning_ai_uxui_web follows in a paired commit.
Two bugs in @bytelyst/data-viz@0.1.0 surfaced during a cross-repo audit:
1. Sparkline used `useMemo(() => \`bl-spark-${Math.random()...}\`)`
for its <linearGradient> id. Math.random() produces different values
during Next.js SSR vs client hydration, triggering React hydration
mismatches (and broken gradient refs on first paint). Swap for
React's `useId()` — deterministic across server + client.
Also: with a single-element series, `data.length > 0` was true but
the early-return branch left `lastX=lastY=0`, painting a stale dot
at the SVG origin. Tighten the guard to `data.length >= 2`.
2. ProgressRing's `Math.max(0, Math.min(1, value))` propagated NaN
when callers passed `NaN` or `Infinity` (e.g. division-by-zero
metrics) — producing "NaN percent" in the aria-label. Guard with
`Number.isFinite` first.
Regression tests cover all three cases — 17/17 passing.
Tests: pnpm -F @bytelyst/data-viz test → 17 passed
═══════════════════════════════════════════════════════════════════════
TODO #6 — size-limit budgets in CI
═══════════════════════════════════════════════════════════════════════
Adds .size-limit.cjs with budgets for 6 pilot @bytelyst/* packages,
plus a Gitea Actions workflow (.gitea/workflows/size-limit.yml) that
fails the build on any regression.
Current measurements vs budget (all comfortably under):
@bytelyst/api-client 793 B / 8 KB
@bytelyst/auth-client 1.97 KB / 8 KB
@bytelyst/celebrations 236 B / 6 KB
@bytelyst/quick-actions 122 B / 6 KB
@bytelyst/react-auth 2.71 KB / 10 KB
@bytelyst/dashboard-shell 3.96 KB / 30 KB
Scripts:
pnpm size — measure + assert against budgets
pnpm size:why — explain top contributors
Deps: size-limit@^12.1.0, @size-limit/preset-small-lib@^12.1.0.
Pilot scope (this commit): 6 packages. Full @bytelyst/* rollout is
incremental — each entry added separately.
═══════════════════════════════════════════════════════════════════════
TODO #5 — Storybook canonical pattern
═══════════════════════════════════════════════════════════════════════
Discovery: @bytelyst/ui already has Storybook 8 with the @bytelyst/
addon-a11y addon configured and 5 .stories.tsx files.
This commit:
- Documents that setup as the canonical template
(docs/STORYBOOK_TEMPLATE.md) including the .storybook/main.ts,
preview.ts, package.json scripts, and an example story.
- Catalogs which of the 8 visual @bytelyst/* packages need Storybook
added (1/8 done — @bytelyst/ui).
- Per docs/ROADMAP_2026_DECISIONS.md §9, hosting target is
self-hosted Gitea Pages (no Chromatic).
Full rollout to the other 7 packages stays open as incremental work.
═══════════════════════════════════════════════════════════════════════
TODO #4 — DTCG v3 token migration RFC
═══════════════════════════════════════════════════════════════════════
This is too large to land in a single commit. Drafted RFC instead:
docs/rfc/0001-dtcg-v3-token-migration.md
The RFC proposes a 4-PR sequence:
PR 1 — Add converter + dual-emit (byte-identical assertion)
PR 2 — Flip source of truth to DTCG JSON
PR 3 — Introduce the component tier
PR 4 — Multi-theme via DTCG token sets
Estimate: 2 person-weeks total. Backward compatibility: --ml-* and
--bl-* names preserved through all 4 PRs. Status: Draft, awaiting
reviewers.
Refs: learning_ai_uxui_web/docs/ROADMAP_2026.md §10 TODOs #4, #5, #6
Three coordinated package changes addressing Wave 1 cross-repo TODOs
from the UI/UX roadmap (learning_ai_uxui_web/docs/ROADMAP_2026.md §10).
═══════════════════════════════════════════════════════════════════════
TODO #2 — @bytelyst/react-auth bump 0.1.8 → 0.2.0
═══════════════════════════════════════════════════════════════════════
- Changes 'workspace:*' to 'workspace:^' for the @bytelyst/api-client
dependency. On pnpm publish this resolves to a caret range (e.g.
^0.1.6) instead of '*', restoring installability for consumers.
(The 0.1.6 tarball was published with a literal 'workspace:*'
string — newer minor bump unblocks the showcase react-auth demo.)
- 21 tests still passing.
═══════════════════════════════════════════════════════════════════════
TODO #3 — @bytelyst/dashboard-shell bump 0.1.7 → 0.2.0
═══════════════════════════════════════════════════════════════════════
- Adds 'routePrefix?: string' prop to DashboardShellProps, SidebarProps,
and TopBarProps. Threads through DashboardShell → Sidebar + TopBar.
- Built-in /profile, /billing, /settings links now use the prefix:
routePrefix="/app" → /app/profile, /app/billing, /app/settings
- Defaults to '' (empty string) — fully back-compat with 0.1.x callers.
- 2 new vitest cases covering both prefixed and default behavior;
43 / 43 tests passing (+2 from 41).
═══════════════════════════════════════════════════════════════════════
TODO #1 — @bytelyst/design-tokens bump 0.1.8 → 0.2.0
═══════════════════════════════════════════════════════════════════════
- Adds a density-aware spacing tier on top of the existing raw
--ml-space-* tier:
--bl-space-scale: 1 (default :root)
--bl-space-1..16: calc(--ml-space-N × --bl-space-scale)
- Emits density selectors at the end of tokens.css:
[data-density="compact"] { --bl-space-scale: 0.875; }
[data-density="comfortable"] { --bl-space-scale: 1; }
[data-density="spacious"] { --bl-space-scale: 1.125; }
- Generator (scripts/generate.ts) emits both tiers automatically; the
auto-generated per-product CSS files (lysnrai, mindlyst, etc.) gain
a single blank-line diff from regeneration — no semantic change.
- 11 / 11 token tests passing.
═══════════════════════════════════════════════════════════════════════
Decision doc — docs/ROADMAP_2026_DECISIONS.md
═══════════════════════════════════════════════════════════════════════
- Records pragmatic defaults for TODO ledger items #9–#13 so
implementation work doesn't block:
#9 Storybook hosting → self-hosted on Gitea Pages (free)
#10 useChat protocol → adopt Vercel AI SDK shape, abstract transport
#11 react-auth fold-in → defer to Wave 7
#12 dashboard-shell merge → defer to Wave 7
#13 mobile-native UI → out of scope (tokens-only sharing)
- Each decision is reversible via RFC.
═══════════════════════════════════════════════════════════════════════
Publish flow
═══════════════════════════════════════════════════════════════════════
These three packages now require a release. The existing publish
workflow (.gitea/workflows/publish-packages.yml) has PACKAGE_FILTER
pinned to @bytelyst/errors and won't pick them up automatically — a
manual workflow_dispatch with a broader filter (or the existing
publish-all-packages.yml on workflow_dispatch) is needed to ship
0.2.0 to the Gitea npm registry.
Refs: learning_ai_uxui_web/docs/ROADMAP_2026.md §10 TODOs #1, #2, #3, #9–#13
- sync-docker-prep.sh: add MindLyst, LysnrAI, talk2obsidian to consumer list
- docker-doctor.sh: detect Python Dockerfiles (python:3.x base) and skip
Node-specific checks (pnpm/corepack, .npmrc.docker ARGs). Python base
images are now in the approved list alongside node:22-{alpine,slim}.
Refs: docker-build-optimization-roadmap.md \xc2\xa7 D
Promotes docker-prep.sh to canonical home in common-plat with full Phase B
hardening from the docker-build-optimization-roadmap:
- B1: --dry-run mode (lists actions, no side effects)
- B2: idempotency guard (refuses to run if *.bak exists, --force to bypass)
- B5: trap-based auto-restore on error (--keep to disable)
- B6: standardized header + usage block
- B7: canonical home + sync + drift-check (mirrors npmrc.template pattern)
- B8: --strip-overrides for safety-net cleanup
- New: --check mode for CI-friendly state verification
- New: auto-discovers package.json files with @bytelyst/* deps
- New: portable sed -i (BSD on macOS, GNU on Linux)
- New: preserves .docker-deps/.gitkeep on clear (fixes earlier regression)
- New: 2 small JS helpers (_docker-prep-*.js) avoid bash 3.2 heredoc quirks
Verified on clock + peakpulse: dry-run, pack, check, idempotency guard,
restore, and post-restore git status all clean.
Copy-pasteable runbook for the case where:
- VM is already provisioned
- Gitea is already installed and running on :3300
- Repos are already cloned on the VM
- User needs to wire admin + npm-user + token + laptop end-to-end
10 numbered steps with expected outputs and troubleshooting:
1. Create Gitea admin user (idempotent skip if exists)
2. Create npm owner user (learning_ai_user)
3. Mint npm-scoped token via API
4. Write token to ~/.gitea_npm_token_home on laptop
5. Update ~/.gitea_vm_host with VM hostname
6. Pre-flight verification via doctor.sh (expects 404 on probe)
7. Publish @bytelyst/* via publish-local-packages.sh
8. End-to-end verification (re-run doctor + smoke-test pnpm install)
9. Optional: backfill historical versions
10. Persist environment in ~/.zshrc
Includes troubleshooting table, persistence map (what survives VM reboot
vs rebuild), and Azure NSG/firewall guidance.
Companion to scripts/gitea/{bootstrap-vm,doctor,token}.sh.
Static linter for Dockerfile + docker-compose + .npmrc.docker drift.
Sibling to gitea-doctor. Codifies all 15 invariants from Phase A of
the docker-build-optimization-roadmap so regressions are caught at
PR time, not at build time.
Verified against both pilots:
- learning_ai_clock: PASS (1 expected warning)
- learning_ai_peakpulse: PASS (1 expected warning, pnpm-lock per ADR-0001)
- learning_ai_notes (un-migrated control): FAIL with 6 specific findings
Refs: docker-build-optimization-roadmap.md \xc2\xa7Phase E (E1, E5)
Resolves F17 in docker-build-optimization-roadmap.
Root cause:
Gitea's app.ini ROOT_URL was http://localhost:3300/. Gitea bakes
ROOT_URL into the dist.tarball field of every published package's
metadata. Inside a Docker container, 'localhost' is the container
itself, not the host \u2014 so any 'pnpm install' that needed to fetch
a tarball would ECONNREFUSED, even though the registry metadata
itself was reachable via host.docker.internal.
Server-side fix (not in git, requires manual replication on each dev
machine; documented in roadmap \u00a73 A-pre-6):
- Edit /opt/homebrew/var/gitea/custom/conf/app.ini:
ROOT_URL = http://host.docker.internal:3300/
- brew services restart gitea
- sudo sh -c 'echo "127.0.0.1 host.docker.internal" >> /etc/hosts'
Repo-side fix (this commit):
- switch-network.sh: add host.docker.internal to NO_PROXY +
NPM_CONFIG_NOPROXY when NETWORK=corp. Required so host-side curl/
pnpm/npm bypass the corporate proxy (cso.proxy.att.com) when
resolving host.docker.internal. Without this, host installs fail
with the corp proxy's 'Unknown Host' 504 page.
Republished all 64 @bytelyst/* packages so tarball URLs reflect the
new ROOT_URL:
- .publish-manifest.json: 64 entries with new content hashes
- packages/*/package.json: 64 patch-version bumps
(auto-bumped by publish-outdated-packages.sh because previous
versions already existed in registry)
Verification:
curl http://localhost:3300/.../@bytelyst%2Ferrors | jq .dist.tarball
→ http://host.docker.internal:3300/.../errors-0.1.11.tgz (was localhost:3300)
workspace:* refs across all 64 packages: 0
Unblocks: A0-V on every pilot. Verified PASSING on learning_ai_clock:
backend cold build: 59.2 s
web cold build: 3:13 (193 s)
Both via Gitea registry, no docker-prep.sh tarballs needed.
Resolves F16 in docker-build-optimization-roadmap v5.
Root cause:
publish-outdated-packages.sh uses a pack-extract-repack pattern:
1. pnpm pack (rewrites workspace:* in tarball)
2. extract
3. npm pack (re-tar from extracted content)
4. npm publish
Step 3 is the bug. npm pack does not recognize the pnpm-specific
workspace: protocol — it treats workspace:* as a literal version
string and passes it through to the final tarball. Result: any
consumer doing 'pnpm install' inside Docker (where there is no
workspace context) fails with ERR_PNPM_WORKSPACE_PKG_NOT_FOUND.
Documented in roadmap §0 F16 + §3 Phase A-pre.
Fix (publish-outdated-packages.sh):
- Insert a workspace:* rewriter between publishConfig strip and
npm pack. Reads source package.json for each @bytelyst/* target,
resolves workspace:* / workspace:^ / workspace:~ to ^x.y.z.
- Add defense-in-depth: grep the post-rewrite package.json for any
surviving 'workspace:' literal. If found, refuse to publish.
Republished 10 affected packages with workspace:* → resolved semver:
@bytelyst/auth 0.1.5 → 0.1.6
@bytelyst/diagnostics-client 0.1.6 → 0.1.7
@bytelyst/events 0.1.5 → 0.1.6
@bytelyst/extraction 0.1.5 → 0.1.6
@bytelyst/fastify-auth 0.1.5 → 0.1.6
@bytelyst/fastify-core 0.1.5 → 0.1.6
@bytelyst/feedback-client 0.1.6 → 0.1.7
@bytelyst/field-encrypt 0.1.6 → 0.1.7
@bytelyst/react-auth 0.1.6 → 0.1.7
@bytelyst/sync 0.1.5 → 0.1.6
Verification: all 10 packages now scan with 0 workspace:* refs in
their published package.json (per registry curl scan).
Unblocks: A0-V verification on learning_ai_clock (currently blocked
at learning_ai_clock@0be887288).
Idempotent end-to-end Gitea bootstrap for Azure VM (or any Linux host
with Docker available). Replaces manual SSH-and-paste workflow.
Steps (each skippable on re-run):
1. Install Docker via official script (skip with --skip-docker)
2. Write /etc/gitea/docker-compose.yml with package registry enabled
3. Start gitea container, wait for HTTP :3300
4. Create admin user via 'gitea admin user create' (CLI inside container,
no auth bootstrap needed)
5. Create npm-user (learning_ai_user) via admin API
6. Mint npm-scoped token with write:package + read:package
Two execution modes:
- On the VM directly: scp + ssh + run
- Locally targeting remote: --ssh-host azureuser@vm
Outputs npm token to --output FILE or stdout. Prints copy-paste-ready
command for writing to ~/.gitea_npm_token_home on the workstation.
Final summary prints the doctor.sh verification command so user can
confirm registry reachability from their laptop in one step.
--dry-run shows planned actions without execution.
--force re-creates users (use after manual deletion).
Closes the 'cloud VM bootstrap' gap identified during the Gitea hardening
review — pairs with scripts/gitea/{doctor,token}.sh from commit 610a59fd.