Phase 6 of the Skills + Revisions + Proposals work. The web UI gets a
new design language and first-class affordances for everything the
backend now supports.
## Visual direction
- Tailwind v4 with custom @theme block (oklch tokens). Dark-mode-only
(internal tool — light mode doubles QA surface).
- Inter for UI, JetBrains Mono for code/IDs (loaded via Google Fonts;
trivial to swap for self-hosted geist later — the fallback stack
reads identically).
- Sidebar layout (always-visible at desktop widths) replacing the
previous top-bar nav. Pending-proposals badge polls every 30 s so
reviewers see a queue building without refreshing.
- Lucide icons throughout.
- Spacing and radii on Tailwind defaults.
Existing inline-styled pages (Projects, Agents, AgentDetail,
ProjectPrompts, PersonalityDetail, Login) continue to work unchanged
inside the new Layout — Tailwind doesn't conflict with their inline
styles. A follow-up can migrate them incrementally.
## What's added
### Build infra (src/web/)
- package.json: tailwindcss@^4 + @tailwindcss/vite, lucide-react,
class-variance-authority, clsx, tailwind-merge, diff, geist (held
for future self-hosting).
- vite.config.ts: registers the @tailwindcss/vite plugin.
- src/index.css: Tailwind import + @theme tokens + @layer base.
- src/main.tsx: imports index.css.
- src/lib/utils.ts: shadcn-style cn() helper.
### shadcn-style primitives (src/components/ui/)
Hand-written rather than generated via `npx shadcn` so the repo doesn't
depend on a CLI tool that needs interactive runtime:
- button.tsx — variants: primary / secondary / ghost / danger / link;
sizes: sm / md / lg / icon.
- card.tsx — Card + Header/Title/Description/Content/Footer subparts.
- badge.tsx — variants: default / info / success / warning / danger /
outline.
- input.tsx — Input + Textarea + Label.
- tabs.tsx — no-dep accessible Tabs (no Radix needed for our use).
- separator.tsx — h/v separator with role=separator.
### Diff component (src/components/Diff.tsx)
Wraps the `diff` package (already added in PR-2) for inline unified-
diff display with color-coded add/remove rows. Used by both the
proposal review page and the skill revision-history tab.
### New pages (src/pages/)
- Dashboard.tsx — at-a-glance home. Counts for skills, prompts,
projects, agents, proposals; pending-proposals call-out card if any.
- Skills.tsx — list view, separated into Global vs Project/Agent-
scoped sections.
- SkillDetail.tsx — name + semver + description; tabs for SKILL.md /
Files / Metadata / History. History tab shows revisions with
click-to-diff against the live body.
- Proposals.tsx — queue with Pending/Approved/Rejected tabs. Pending
count is highlighted in amber.
- ProposalDetail.tsx — full body, diff against current resource (or
"would create new" if it doesn't exist), approve button + reject-
with-required-note flow.
### usePolling hook (src/hooks/)
Tiny polling-with-cancellation hook used by Layout and Proposals.
### Layout rewrite (src/components/Layout.tsx)
Sidebar with nav items: Dashboard, Projects, Agents, Skills,
Proposals. Lucide icons. Active-route highlighting via NavLink.
Pending-proposals warning badge on the Proposals item.
### Routes (src/App.tsx)
New routes: /dashboard, /skills, /skills/:name, /proposals,
/proposals/:id. Default redirects to /dashboard.
### API types (src/api.ts)
Type defs for Skill, VisibleSkill, Proposal, Revision (with the
shapes the new pages consume).
## Tests
Existing 7 web tests still pass (Login + api). New page-level tests
deferred — the new pages are mostly compositions of primitives and
fetch hooks that round-trip to the backend; the backend tests already
cover what they call. PR-7 polish can add render-and-click tests if
coverage drift surfaces.
## Verification
- `pnpm --filter @mcpctl/web build` clean, no warnings.
- `pnpm test:run` whole monorepo: 162 test files / 2157 tests green.
- Visual smoke deferred — needs a running mcpd to populate the
fixtures. Manual smoke tested locally is the next step.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>