Skip to content

feat: migrate dashboard to React + Zustand + Tailwind - #3

Merged
aterrylu merged 2 commits into
mainfrom
terry/react-migration
Mar 7, 2026
Merged

feat: migrate dashboard to React + Zustand + Tailwind#3
aterrylu merged 2 commits into
mainfrom
terry/react-migration

Conversation

@aterrylu

@aterrylu aterrylu commented Mar 7, 2026

Copy link
Copy Markdown
Owner

Summary

Migrates the dashboard from vanilla TypeScript DOM manipulation to React + Zustand + Tailwind (ADR-010).

Every comparable product in our research uses React (Zo Computer, Mission Control, LM Studio, YepAnywhere). This migration sets us up for multi-session UI, panels, and richer dashboard features.

Architecture

src/
├── main.tsx          # React root mount
├── App.tsx           # Layout shell
├── store.ts          # Zustand store (theme, session, status)
├── index.css         # Tailwind + scrollbar styles
├── components/
│   ├── Header.tsx    # Status, theme toggle, new session
│   └── TerminalView.tsx  # xterm.js container
└── hooks/
    └── useTerminal.ts    # xterm.js lifecycle + WebSocket

What changed

  • Vanilla TS → React components with Zustand state management
  • Inline CSS → Tailwind utility classes
  • Theme/session/status state → single Zustand store
  • xterm.js lifecycle → useTerminal custom hook
  • VSCode-style scrollbar (visible on hover, proportional thumb)
  • Added ADR-010 to DECISIONS.md

What didn't change

  • All existing functionality: themes, keybindings, resize, reconnect
  • Server, core, and API — untouched
  • Hono stays as the backend (no Next.js)

Test plan

  • make check passes (lint + typecheck + 18 tests)
  • Create session, type in terminal
  • Theme cycling (Midnight → Daylight → Void) with persistence
  • Terminal resize on window drag
  • Cmd+K clear, Option+word nav, Cmd+Backspace (macOS)
  • Scrollbar visible on hover, proportional to content

🤖 Generated with Claude Code

- Replace vanilla TS DOM manipulation with React components
- Add Zustand store for theme, session, and status state
- Add Tailwind CSS via @tailwindcss/vite plugin
- Extract terminal logic into useTerminal hook
- Component structure: App, Header, TerminalView
- VSCode-style scrollbar (visible on hover, proportional thumb)
- All existing functionality preserved (themes, keybindings, resize)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@nox-0x nox-0x left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Clean React migration. Store structure is solid, theme cycling works correctly, Vite proxy config is unchanged. LGTM.

- Remove StrictMode to prevent xterm.js/WebGL double-mount issues in dev
- Add spawn guard on New Session button to prevent racing sessions
- Remove unused API_URL constant and observerRef
- Simplify fetch error handling with .catch(() => null) pattern
- Condense verbose callbacks to concise arrow expressions
- Move container.replaceChildren() into effect cleanup
- Extract isThemeName type guard for proper type narrowing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@nox-0x nox-0x left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

React migration looks solid. Clean component decomposition, proper cleanup in useTerminal (ResizeObserver + WebSocket + terminal.dispose), Zustand store handles localStorage persistence correctly. LGTM 🚀

@aterrylu
aterrylu merged commit efec04f into main Mar 7, 2026
1 check passed
aterrylu added a commit that referenced this pull request May 13, 2026
…ss semantics

CI failure: biome's organizeImports rule flagged the parseCliArgs call sandwiched
between import groups. Reviewer @nox-0x flagged the same issue separately.
Moved all imports to the top, then the parseCliArgs + --help short-circuit, then
the rest of the imperative startup work. Also added a comment clarifying that
--help only avoids imperative work, not import-time side effects (since ESM
hoists all imports regardless).

Review observation #3 (readiness semantics): AUTONOMOS_READY signal means "HTTP
listener accepting connections" — NOT "agents fully hydrated." Gateway init,
resumeActiveAgents, and scheduler startup run in the same tick but may finish
slightly later. Documented this in:
  - embedded-mode.ts (next to the signal emitter)
  - phase-1b-sketch.md (in the integration contract section)

So Phase 1B's Electron shell can load the webview as soon as the signal fires,
with the understanding that "fully populated UI" may lag by ~ms.

Verified:
- npx biome check packages/ → clean (only pre-existing warnings unrelated to 1A.1)
- ./scripts/test-1a1-isolated.sh → all checks still pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
aterrylu added a commit that referenced this pull request May 13, 2026
* feat(server): Phase 1A.1 — bundleable server foundation for desktop app

Adds the runtime contract + build pipeline that Phase 1B's Electron desktop app
will use to spawn the autonomos-server as a child process. Purely additive — the
existing make dev / make prod / pm2 paths continue to work unchanged.

What lands:
- packages/server/src/cli-args.ts: --port and --embedded argv parser, --help
- packages/server/src/embedded-mode.ts: localhost-only bind + AUTONOMOS_READY
  stdout signal for parent-process discovery
- packages/server/src/index.ts: wires both, also fixes a latent fragility where
  existsSync(dashboardDist) returned true on a tsc-only artifact dir (now checks
  for index.html explicitly, prefers _embedded_dashboard over fallback path)
- packages/server/build/embed-dashboard.ts: copies dashboard/dist into the
  server tree so the bundler sees it
- packages/server/build/build-binary.ts: orchestrates bun build --target=node,
  copies embedded dashboard next to bundled JS for runtime resolution
- scripts/test-1a1-isolated.sh: full isolated smoke test (separate config dir,
  dedicated port, never touches ~/.autonomos/)
- docs/research/: consolidated design notes + Phase 1A.1 proposal + Phase 1B/1C
  sketches capturing the full plan

Build deviation from original proposal: bun build --compile is blocked by a
hard ABI mismatch between Bun 1.3.10 (ABI 137) and node-pty's prebuilt
(ABI 141). Both the static-binary path AND --target=bun fail because the Bun
runtime can't load node-pty at all. Pivoted to --target=node so the bundle
runs under Node, which loads node-pty cleanly. Phase 1B's Electron bundles
Node + this bundle. Static-binary aspiration deferred until either Bun's ABI
matches or PTY is refactored to Bun's native API.

Smoke test results (./scripts/test-1a1-isolated.sh on darwin-arm64):
  ✓ AUTONOMOS_READY signal received in 2s
  ✓ /api/host returns 200
  ✓ Dashboard / serves embedded HTML
  ✓ SIGTERM triggers clean shutdown in 2s
  ✓ ~/.autonomos/ untouched (test ran in isolation)
  ✓ make dev path still boots cleanly (regression check)

Contract Phase 1B will rely on:
  Spawn: node dist/<platform>/index.js --port=0 --embedded
  Discovery: parse "AUTONOMOS_READY port=<N>" from child stdout
  Shutdown: send SIGTERM, child exits within ~2s

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(server): address PR #169 review — group imports, document readiness semantics

CI failure: biome's organizeImports rule flagged the parseCliArgs call sandwiched
between import groups. Reviewer @nox-0x flagged the same issue separately.
Moved all imports to the top, then the parseCliArgs + --help short-circuit, then
the rest of the imperative startup work. Also added a comment clarifying that
--help only avoids imperative work, not import-time side effects (since ESM
hoists all imports regardless).

Review observation #3 (readiness semantics): AUTONOMOS_READY signal means "HTTP
listener accepting connections" — NOT "agents fully hydrated." Gateway init,
resumeActiveAgents, and scheduler startup run in the same tick but may finish
slightly later. Documented this in:
  - embedded-mode.ts (next to the signal emitter)
  - phase-1b-sketch.md (in the integration contract section)

So Phase 1B's Electron shell can load the webview as soon as the signal fires,
with the understanding that "fully populated UI" may lag by ~ms.

Verified:
- npx biome check packages/ → clean (only pre-existing warnings unrelated to 1A.1)
- ./scripts/test-1a1-isolated.sh → all checks still pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
aterrylu added a commit that referenced this pull request Jun 6, 2026
…LOG (pipeline PR 1/6) (#181)

build(release): adopt changesets — automated versioning + single root CHANGELOG

PR #1 of the professional release pipeline (approved design, macOS-only desktop).
Replaces the manual `sed`-across-5-package.json version bump that caused the
0.0.2-code / v0.0.1-tag drift.

## What this adds

- **changesets** (`@changesets/cli` + `@changesets/changelog-github`) with a
  `fixed` group covering all 5 packages → they always version in lockstep to a
  single version. Devs declare bumps via `bun run changeset` (a 3-line file per
  user-facing PR); see `.changeset/README.md`.
- **Single root `CHANGELOG.md`** (Keep-a-Changelog), seeded with the hand-written
  0.0.1 + 0.0.2 history. From the next release on it's generated automatically.
  Per-package changelogs are gitignored — `scripts/sync-changelog.ts` promotes
  each new version's section into the root one after `changeset version`.
- **`scripts/release-notes.ts`** extracts a version's CHANGELOG section for the
  GitHub Release body (wired into release.yml in PR #3).
- **`.github/workflows/version.yml`** — maintains the "Version Packages" PR and,
  when it merges, auto-tags `vX.Y.Z` to trigger the release build. Releasing
  becomes "merge the Version Packages PR" — no manual version edits, ever.
- **`.github/workflows/changeset-check.yml`** — informational PR nudge to include
  a changeset (never blocks; trivial PRs use `--empty`).
- Marked `cli`/`core`/`dashboard`/`server` **private** — they're internal
  workspace packages, not npm publishes (prevents accidental publish + clarifies
  intent to changesets).

## Validated locally (not just claimed)

- `bun run version` with a throwaway minor changeset → all 5 packages bumped
  0.0.2 → 0.1.0 in lockstep, root CHANGELOG got a dated section, then reverted.
- `bun scripts/release-notes.ts 0.0.2` extracts the right section.
- `make check`: 353/353 tests pass; `biome check packages/` clean.

## Dormant until PR #3

No changeset is included in this PR on purpose — the version machinery stays
dormant (no Version PR / tag) until the new `release.yml` lands in PR #3. The
changeset-check will warn on this infra PR; that's expected.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
aterrylu added a commit that referenced this pull request Jun 6, 2026
Encodes the "run CI locally before push" discipline as enforcement instead of
memory — the exact failure mode that caused repeated red-CI pushes.

## Hooks (lefthook)

- **pre-commit** — `biome check` (no --write) on staged package files. Fails on
  lint/format issues rather than silently fixing, mirroring CI.
- **commit-msg** — commitlint (Conventional Commits, loose rules).
- **pre-push** — the FULL CI gate: `biome check packages/ && make check`. If this
  passes, the PR's `check` job will. Emergency bypass: `git push --no-verify`.

## commitlint (loose, hygiene not gatekeeping)

Since changesets owns versioning, commit messages aren't release-critical — the
config is permissive: project commit types incl. repo-specific `init`/`research`,
any subject case, no body-line-length policing (our commits carry URLs + code).

`prepare` script runs `lefthook install` on `bun install` so hooks activate for
everyone automatically.

## Validated locally (real, dogfooded)

- commitlint: accepts `feat(app): ...`, rejects `added some stuff`, allows our
  long-body style.
- `lefthook run pre-push`: ran biome + make check → 353/353 tests, 19s.
- This very commit + push runs through the new commit-msg + pre-push hooks.

Stacked on #181 (changesets). No changeset — infra PR, machinery stays dormant
until release.yml lands in PR #3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
aterrylu added a commit that referenced this pull request Jun 6, 2026
build(release): add lefthook + commitlint git hooks (pipeline PR 2/6)

Encodes the "run CI locally before push" discipline as enforcement instead of
memory — the exact failure mode that caused repeated red-CI pushes.

## Hooks (lefthook)

- **pre-commit** — `biome check` (no --write) on staged package files. Fails on
  lint/format issues rather than silently fixing, mirroring CI.
- **commit-msg** — commitlint (Conventional Commits, loose rules).
- **pre-push** — the FULL CI gate: `biome check packages/ && make check`. If this
  passes, the PR's `check` job will. Emergency bypass: `git push --no-verify`.

## commitlint (loose, hygiene not gatekeeping)

Since changesets owns versioning, commit messages aren't release-critical — the
config is permissive: project commit types incl. repo-specific `init`/`research`,
any subject case, no body-line-length policing (our commits carry URLs + code).

`prepare` script runs `lefthook install` on `bun install` so hooks activate for
everyone automatically.

## Validated locally (real, dogfooded)

- commitlint: accepts `feat(app): ...`, rejects `added some stuff`, allows our
  long-body style.
- `lefthook run pre-push`: ran biome + make check → 353/353 tests, 19s.
- This very commit + push runs through the new commit-msg + pre-push hooks.

Stacked on #181 (changesets). No changeset — infra PR, machinery stays dormant
until release.yml lands in PR #3.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
aterrylu added a commit that referenced this pull request Jun 6, 2026
The written runbook the pipeline was missing — releasing is no longer tribal
knowledge.

- docs/RELEASE.md: the full runbook. Mental model (changesets → Version PR →
  auto-tag → release.yml), day-to-day changeset ritual, how to cut a release,
  beta/pre-release, rollback, local DMG builds, the secrets table, and
  troubleshooting.
- docs/DECISIONS.md ADR-031: the release-pipeline decision record — context
  (version drift + hand-built DMG), the 6-PR design, scope decisions (macOS-only
  desktop, universal2 from day one, changesets over release-please), rationale,
  alternatives, and implications.

ADR-031 is team-lead-owned — drafted here, flagged for review. Numbering note:
ADR-030 informally forward-referenced "ADR-031" for unbuilt named-profiles; this
ADR claims it for the release pipeline (the real decision), noted in the entry.

SLSA provenance (the other half of PR #6) edits release.yml, which lives in the
unmerged PR #3 — deferred to a follow-up once #3 lands to avoid a conflict.

Docs-only, no version impact (empty changeset).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
aterrylu added a commit that referenced this pull request Jun 6, 2026
docs(release): add RELEASE.md runbook + ADR-031 (pipeline PR 6/6, docs)

The written runbook the pipeline was missing — releasing is no longer tribal
knowledge.

- docs/RELEASE.md: the full runbook. Mental model (changesets → Version PR →
  auto-tag → release.yml), day-to-day changeset ritual, how to cut a release,
  beta/pre-release, rollback, local DMG builds, the secrets table, and
  troubleshooting.
- docs/DECISIONS.md ADR-031: the release-pipeline decision record — context
  (version drift + hand-built DMG), the 6-PR design, scope decisions (macOS-only
  desktop, universal2 from day one, changesets over release-please), rationale,
  alternatives, and implications.

ADR-031 is team-lead-owned — drafted here, flagged for review. Numbering note:
ADR-030 informally forward-referenced "ADR-031" for unbuilt named-profiles; this
ADR claims it for the release pipeline (the real decision), noted in the entry.

SLSA provenance (the other half of PR #6) edits release.yml, which lives in the
unmerged PR #3 — deferred to a follow-up once #3 lands to avoid a conflict.

Docs-only, no version impact (empty changeset).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants