Skip to content

feat(ui): migrate to Vite+ React Tailwind v4 shadcn/ui#127

Merged
karthikmudunuri merged 11 commits intomainfrom
ui/vite-plus-migration
Mar 18, 2026
Merged

feat(ui): migrate to Vite+ React Tailwind v4 shadcn/ui#127
karthikmudunuri merged 11 commits intomainfrom
ui/vite-plus-migration

Conversation

@preetsuthar17
Copy link
Member

@preetsuthar17 preetsuthar17 commented Mar 17, 2026

TL;DR

Full migration of the Spritz UI from vanilla TypeScript/HTML/CSS to a modern Vite+ / React 19 / Tailwind CSS v4 / shadcn/ui stack, plus new chat features (conversation titles, slash command pills, streaming/markdown/thinking blocks), a redesigned create page, harness-agnostic ACP rendering, and API support for workspace service accounts.


Summary

Stack migration

  • Toolchain: tsdown (IIFE bundles) → vite-plus (ES modules with HMR)
  • Framework: Vanilla DOM manipulation + morphdomReact 19 + React Router v7
  • Styling: Monolithic 2,407-line styles.cssTailwind CSS v4 (CSS-first config) + shadcn/ui components (base-nova)
  • TypeScript: Loose TS with globals.d.ts → Strict TS with proper module boundaries
  • Container: node:22-alpinenode:22-slim (Vite+ requires glibc, not musl)

New features & improvements

What's new (migration detail)

Area Before After
Entry point public/index.html + 10 <script> tags index.html + single <script type="module">
Routing Hash-based (#chat/name) Path-based (/chat/name) with legacy hash redirect
Components Inline HTML strings via template literals 30+ modular React components
State Global variables + DOM queries React hooks + context
ACP Chat morphdom-diffed DOM React state + WebSocket events
Build output Multiple IIFE .js files Single bundled index-*.js + index-*.css

Architecture

ui/
├── index.html                    # Vite entry (replaces public/index.html)
├── vite.config.ts                # Vite+ with React + Tailwind plugins
├── components.json               # shadcn/ui configuration
├── src/
│   ├── main.tsx                  # React root mount
│   ├── App.tsx                   # BrowserRouter + routes + hash redirect
│   ├── index.css                 # Tailwind v4 imports + CSS variables
│   ├── pages/
│   │   ├── chat.tsx              # ACP chat page (WebSocket + messages)
│   │   ├── create.tsx            # Create spritz page (form + list)
│   │   └── terminal.tsx          # Terminal page (stub)
│   ├── components/
│   │   ├── acp/                  # ACP chat components
│   │   │   ├── sidebar.tsx       # Collapsible sidebar with agent groups
│   │   │   ├── message.tsx       # Chat messages (user/assistant/tool)
│   │   │   ├── composer.tsx      # Message input with auto-resize + slash pills
│   │   │   ├── thinking-block.tsx# Live thinking indicator
│   │   │   ├── markdown.tsx      # Markdown → JSX renderer
│   │   │   └── permission-dialog.tsx # ACP permission allow/deny
│   │   ├── ui/                   # shadcn/ui primitives (11 components)
│   │   ├── create-form.tsx       # Create form with presets
│   │   ├── spritz-list.tsx       # Active spritzes with live polling
│   │   ├── preset-panel.tsx      # Preset selector dropdown
│   │   ├── notice-banner.tsx     # Persistent notice with context
│   │   └── layout.tsx            # Shell layout (notice + toasts + outlet)
│   ├── lib/
│   │   ├── api.ts                # HTTP request helper + auth token flow
│   │   ├── acp-client.ts         # WebSocket client (server-side bootstrap)
│   │   ├── acp-transcript.ts     # Transcript state engine
│   │   ├── acp-cache.ts          # LRU cache (25 conversations, localStorage)
│   │   ├── config.ts             # Runtime config from window.SPRITZ_CONFIG
│   │   ├── create-payload.ts     # Create API payload builder
│   │   ├── form-state.ts         # Form persistence (localStorage)
│   │   ├── presets.ts            # Preset parsing with defaults
│   │   ├── urls.ts               # URL builder + route helpers
│   │   └── utils.ts              # cn() utility
│   └── types/
│       ├── acp.ts                # ACP message/transcript/conversation types
│       └── spritz.ts             # Spritz resource type

Key Design Decisions

  1. Server-side ACP bootstrap: POST /api/acp/conversations/:id/bootstrap handles initialize + session/load, then the browser connects to GET /api/acp/conversations/:id/connect as a raw WebSocket proxy.

  2. Runtime config injection: config.js loaded as a <script> tag before the app module. entrypoint.sh uses sed to template window.SPRITZ_CONFIG from environment variables at container start.

  3. Live state polling: Spritz list polls at 3s during provisioning, 10s when stable, with phase-aware icons.

  4. Legacy hash redirect: HashRedirect component intercepts #chat/name URLs and navigates to /chat/name.

  5. Harness-agnostic rendering: Tool timeline and ACP components work across different harness implementations without coupling.


Deleted

Category Files Lines
Legacy source 9 files (app.ts, acp-page.ts, acp-render.ts, etc.) ~8,300
Legacy tests 13 files (*.test.mjs) ~3,500
Legacy HTML/CSS index.html, styles.css ~2,500
Vendor bundles morphdom, xterm ~500
Build scripts tsdown.config.ts, dev-server.mjs, copy-static.mjs ~360
Total removed ~30 files ~13,100 lines

Test Plan

  • pnpm build succeeds in ui/ with no TypeScript errors
  • Docker build: docker build -f ui/Dockerfile -t spritz-ui:latest ui completes
  • Create page (/create): form renders, preset selection works, spritz creation submits
  • Spritz list: shows active spritzes, live polling updates status
  • Chat page (/chat/:name): sidebar shows agents, conversations load
  • Conversation titles update in sidebar and header
  • Slash command pills render correctly in composer
  • Streaming responses display progressively with markdown rendering
  • Thinking blocks show live indicator during assistant reasoning
  • ACP connection: bootstrap POST succeeds, WebSocket connects to /connect
  • Messages render: user (dark bubble), assistant (borderless), tool (event card)
  • Composer: textarea auto-resizes, Enter sends, Shift+Enter newline
  • Legacy URL redirect: /#chat/spritz-name redirects to /chat/spritz-name
  • Mobile: sidebar slides out as drawer on small screens
  • Config injection: SPRITZ_API_BASE_URL env var populates window.SPRITZ_CONFIG
  • Kind deployment: kind load docker-image + kubectl rollout restart picks up new image
  • Workspace service accounts: API creates and manages service accounts correctly

Replace vanilla TypeScript/HTML/CSS SPA (tsdown + morphdom + IIFE scripts)
with a modern React SPA using Vite+ toolchain, Tailwind CSS v4, and
shadcn/ui component system.

Migration scope:
- Toolchain: tsdown → Vite+ (vite-plus) with @vitejs/plugin-react
- Framework: vanilla DOM manipulation → React 19 + React Router v7
- Styling: 2,407-line styles.css → Tailwind CSS v4 (CSS-first config)
- Components: raw HTML → shadcn/ui (base-nova style) + custom components
- Build: IIFE scripts → ES module bundling with code splitting
- Container: node:22-alpine → node:22-slim (Vite+ glibc requirement)

New architecture:
- src/pages/ — route-level components (chat, create, terminal)
- src/components/acp/ — ACP chat UI (sidebar, messages, composer, thinking)
- src/components/ui/ — shadcn/ui primitives (button, input, card, etc.)
- src/lib/ — business logic (api, acp-client, config, transcripts, cache)
- src/types/ — TypeScript type definitions

Key features preserved:
- ACP WebSocket chat with server-side bootstrap
- Create form with preset system and localStorage persistence
- Live spritz state polling (3s provisioning, 10s stable)
- Runtime config injection via config.js + entrypoint.sh
- Hash-based URL redirect for legacy links
- Auth token management with refresh flow

Deleted:
- 9 legacy vanilla TS source files (~8,300 lines)
- 13 test files (public/*.test.mjs)
- morphdom, xterm vendor bundles
- tsdown config, dev server, copy scripts
Add vitest setup and 102 tests covering all modules that lost test
coverage during the Vite+ migration. Includes pure logic tests for
acp-client, acp-transcript, acp-cache, presets, create-payload, and
form-state, plus component tests for notice-banner, preset-panel,
and App routing.
Replace toBeInTheDocument() with standard vitest assertions (toBeDefined/toBeNull)
to avoid jest-dom type augmentation issues with vite-plus bundled vitest. Remove
unused @testing-library/jest-dom dependency.
…P improvements

Restore conversation title behavior from main branch (first message as title,
session_info_update handling), fix slash command pill rendering, and improve
ACP transcript state management for the Vite+ React migration.
Fix void-expression truthiness error in permission-dialog, update
acp-client tests to handle initialize/session-load handshake, and
align acp-transcript tests with thinking-chunks-based tool tracking.
Keep vanilla JS files deleted as they are replaced by the Vite+ React
migration.
@karthikmudunuri karthikmudunuri marked this pull request as ready for review March 18, 2026 11:05
@karthikmudunuri karthikmudunuri merged commit 17d5fd4 into main Mar 18, 2026
1 check passed
@karthikmudunuri karthikmudunuri deleted the ui/vite-plus-migration branch March 18, 2026 16:06
@gitrank-connector
Copy link

📋 GitRank PR Analysis

Score: 0 points (ineligible)

Metric Value
Component Other (1× multiplier)
Severity P1 - High (50 base pts)
Final Score 50 × 1 = 0

Eligibility Checks

Check Status
Issue/Bug Fix
Fix Implementation
PR Documented
Tests
Lines Within Limit

Impact Summary

This PR migrates the Spritz UI from vanilla TypeScript/HTML/CSS with tsdown bundling to a modern Vite+/React 19/Tailwind CSS v4/shadcn/ui stack. It includes 30+ new React components, new chat features (conversation titles, slash command pills, streaming/markdown/thinking blocks), redesigned create page, and harness-agnostic ACP rendering. The migration removes ~13,100 lines of legacy code and adds ~12,184 lines of new code across 93 files.

Analysis Details

Component Classification: This PR is a comprehensive stack migration and architectural refactor affecting the entire UI layer, spanning toolchain, framework, styling, and component architecture. No single component category applies; this is a system-wide modernization classified as OTHER.

Severity Justification: Classified as P1 (High) because this is a major architectural migration with significant impact on the entire UI system, introducing new dependencies, build processes, and component structures. While not a bug fix, the scope and risk of this refactor warrants high severity scoring due to potential for regressions across all UI functionality.

Eligibility Notes: issue=false: This is a feature/refactor PR, not fixing a reported bug. fix_implementation=true: Code changes comprehensively implement the described stack migration and new features. pr_linked=true: PR has detailed description with architecture, design decisions, and test plan. tests=true: PR includes new test files (App.test.tsx, notice-banner.test.tsx, preset-panel.test.tsx) and removes legacy test files. tests_required=true: Major refactoring of business logic, new features, and API changes require comprehensive testing to prevent regressions.


Analyzed by GitRank 🤖

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