Match the captured card (rec-1): rounded-xl mx-6 ring-1 bg-neutral-900 with
not-last:border-b rows (was divide-y bg-surface-warm); row label is the
prominent text (flex-1 text-sm font-medium) and the value is muted text-xs
(previously reversed). Body uses gap-5 with hero px-6 / card+access mx-6
(was a uniform p-3). Verified by rendering the panel against the capture.
86 unit + 6 panel E2E green.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The panel app header now matches WorkClaw's measured pattern (dark captures
agent-settings / files-explorer / edit-profile):
- App TOP screen: the header lives on the app surface (sticky), showing the
app name (text-lg font-semibold) + a size-7 rounded-full close-X → home,
at px-5 pt-4 pb-3 — replacing the old px-8 pt-6 chevron+small-title. The
home screen keeps its dot + "{agent}'s Computer" header.
- Sub-screens (AppShell SubViewHeader): back-chevron PILL + text-lg
font-semibold title at px-5 pt-4 pb-3 (was text-xs px-3 pt-3 pb-2).
- Settings hero: 96px squircle avatar + text-xl name + text-sm role (matches
the capture; reviewer's 126px was wrong). New Avatar `settings` size.
p3-panel E2E updated (home nav is now the "Close" button). typecheck/lint/
86 unit/31 E2E/6 visual all green; no visual baseline change.
Known follow-up (P2): Settings/Files sub-screens still stack the top header +
the back-header (pre-existing) — to be unified per-app.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The far-right home tile read "Add" and rendered its plus through GradientGlyph
(coral, faint/unclear). Match WorkClaw's capture: label "Add Apps" and a plain
lucide-plus in currentColor (white/65, inherits from the dashed tile),
strokeWidth 2, 19px. Updated the p3-panel E2E label + computer-home baseline.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
All verified against the captured WorkClaw panel DOM (not the reviewer's
live-preview guesses, which were wrong on two points):
- Glyphs are now FILLED — gradient on fill + stroke, 1px stroke, and a
drop-shadow(0 1px 2px rgba(0,0,0,0.25)) — via a `filled` prop on
GradientGlyph. Applied to the dark-tile glyphs (Claw Chat + dock
Skills/Files/Routines/Settings). The reviewer said keep Skills outline —
the capture shows WorkClaw fills the zap too; and it missed the drop-shadow.
- Grid inset 12px → 32px (px-8): the grid is now 336px so grid-cols-4 resolves
to 72px tracks (capture shows repeat(4,1fr) at 336px — NOT explicit 72px cols
as claimed; same grid-cols-4 as ours, just the right container width).
- Panel header: drop the fixed h-11/px-3, use px-8 pt-6 = padding 24px 32px 0
(matches WorkClaw's inline style exactly).
typecheck/lint/86 unit/31 E2E/6 visual green; computer-home baseline updated.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Forensic capture of the live app's Computer panel gave exact ground truth,
correcting my prior commit (which trusted a reviewer's measurements):
- Dock is content-width (~380px) centered by a w-full wrapper, NOT stretched
full-width — my last change had wrongly stretched it. Reverts to the real
centered-pill behaviour.
- Dark tiles (#1f1f1f) gain the measured grounding drop in --shadow-dock-tile
(0 4.65px 9.35px rgba(0,0,0,0.2)); previously flat (hairline + inset only).
- AppIcon cells fixed to 68px / 8px gap; grid gap 34px×16px; icons 34px; the
dock now uses the same nested tile component as the grid.
- White brand-tile shadow + tile nesting were already exact (capture confirms).
Regenerated computer-home baseline. typecheck/lint/86 unit/31 E2E/6 visual green.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Playwright tool to mirror our own WorkClaw app's compiled front-end (CSS,
JS, fonts, SVGs) and dump each screen's DOM + computed styles, for matching
Clawmates to the real thing instead of guessing from screenshots.
Auth is by hand (you log in, incl. the emailed code, it reuses the session);
`live` mode does login + capture in one go for OTP logins. out/ + the one-off
grab scripts are gitignored; eslint ignores the captured minified assets.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Two measured parity changes to the Computer home screen (HomeScreen.tsx):
- Dock: mx-auto centered pill → flex items-end justify-center, so it spans
the panel width as a bottom-anchored frosted bar instead of a floating
content-width pill. (Shadow/blur/bg/ring already matched prod.)
- App-grid tiles: adopt WorkClaw's nested DOM (cell → button → fade layer →
visual tile) and the measured 4-layer "physical icon" shadow on the white
brand tiles (browser/slack), where its dark hairline + drop + bright inset
read. Dark glass tiles keep the white-bevel shadow-dock-tile (a dark drop
is invisible on them). Brand icons sized to the measured 34px.
Regenerated computer-home visual baseline. typecheck/lint/86 unit/31 E2E
green; all 6 visual pass.
Note: WorkClaw's Computer panel is behind auth, so unlike the token work
these targets couldn't be verified against prod CSS — applied per the
reviewer's live measurements at the user's direction.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Verified against workclaw.com's own compiled CSS (.dark scope defines
--surface-warm-muted:#1f1f1f). Used by 11 components (composer, credits,
welcome chips, tabs, app tiles). Subtle — below the visual-diff threshold,
so baselines are unchanged — but it's the correct prod value.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The app sets the `.dark` class but never `color-scheme`, so the browser
renders native UI (scrollbars, form controls, select/date popups,
autofill) in its light default over the dark app — the one real gap a
panel review surfaced. Set `color-scheme: dark` on `.dark`, and reset it
to `light` on the `.marketing` scope (it lives inside the dark <html> and
color-scheme inherits).
The review's other claims were verified against the compiled CSS and are
non-issues: shadow utilities (shadow-dock-tile/screen/…) ARE generated
with values inlined into --tw-shadow (Tailwind v4 doesn't emit them as
root vars); --computer-width/--app-origin-scale are runtime inline styles,
not :root tokens; all 54 @property --tw-* are present.
typecheck/lint/86 unit/31 E2E green; all 6 visual baselines unchanged.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Replace the hand-rolled @theme color block with a real shadcn init shape:
bare :root (light) + .dark (dark) tokens mapped to --color-* via
@theme inline, plus the missing semantic/chart/radius tokens. --accent
is now neutral (#262626) to match WorkClaw; the ~16 coral-via-accent
usages migrate to the dedicated `coral` brand token so they stay coral.
Adds cn() (clsx + tailwind-merge) and components.json so future
`npx shadcn add` works. Clawmates-specific tokens (coral brand,
marketing palette, measured shadows, motion, layout, type scale) are
preserved verbatim in a separate @theme block. Root <html> gains `dark`
(app/login read .dark; marketing stays light via explicit colors).
We do NOT add shadcn's Button/Card/etc. — they'd conflict with the
existing measured-to-WorkClaw components.
Verified value-preserving: typecheck/lint/86 unit/31 E2E all green, and
all 6 visual baselines pass with no diff. Compiled + live CSS confirm
bg-accent→#262626 (neutral), text-coral→#f96565, chart tokens present.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The card used h-full, so it stretched to fill the column — on tall windows
it grew past the reference (808px at a 920px viewport) instead of staying a
content-sized panel. WorkClaw centers a fixed ~652px card that floats with
empty space above/below. Now phone/tablet → md:h-[652px] (md:max-h-full so
it still caps on short windows), centered via justify-center on the column
wrapper; full + mobile keep h-full to fill. Verified at a 920px window: card
652px, top 166 / bottom 818 (centered, floating) instead of 808 stretched.
At the 720px test viewport it caps to the column either way, so no baseline
change — full suite (37) green.
Co-Authored-By: Claude Fable 5 <[email protected]>
Dropped the avatar + name + role + online dot from the top of the chat
header (the claw is already shown in the rail and the welcome state). The
header now holds only the right-aligned action cluster (and the device/
Close overlay while the panel is open). Removed the now-unused agent prop
+ Avatar/Agent imports; ChatWorkspace no longer passes agent to the header.
Regenerated chat-welcome + computer-home baselines; full suite (37) green.
Co-Authored-By: Claude Fable 5 <[email protected]>
Measured sidebar parity:
- Container: dropped the aside's px-1 so the rail is the full w-rail 176px
(was 168px inner); added bg-background (solid #0a0a0a, was transparent);
padding now 8px 0 24px (pt-2 pb-6).
- Bottom nav (Skills/Apps/Team/Credits): 14px/400 → 12px/500 (text-xs
font-medium, leading-[1.3]); trailing credits balance likewise.
- Claw-name labels: active 400→600 (font-semibold), inactive 400→500
(font-medium); kept 12px.
Verified live at 1440: rail width 176, bg rgb(10,10,10), nav 12px/500,
active claw name 12px/600. Rail-wide change → regenerated all 7 visual
baselines; full suite (37) green.
Co-Authored-By: Claude Fable 5 <[email protected]>
The <body> carried Tailwind's `antialiased` (-webkit-font-smoothing:
antialiased), which inherits through the whole tree and renders every
chat/message/UI text thinner & lighter than WorkClaw. WorkClaw leaves the
browser default (`auto`), giving the crisper/heavier weight. Removed the
class — body is now just `min-h-full`; computed -webkit-font-smoothing is
`auto`. The font itself was always 14px/400 Geist; only the smoothing
differed. Global rendering change → regenerated all 7 visual baselines;
full suite (37) green.
Co-Authored-By: Claude Fable 5 <[email protected]>
--font-sans was 'var(--font-geist), system-ui, sans-serif'; WorkClaw's
chain includes ui-sans-serif before system-ui. Added it so the fallback
order matches: geist, geist Fallback, ui-sans-serif, system-ui, sans-serif.
Fallback-only — Geist always loads (vendored locally), so rendering is
unchanged; full suite (37, incl. visual) green with no baseline diff.
(The lowercase 'geist' / 'geist Fallback' names are next/font's
auto-generated, metric-adjusted family names — CSS font-family matching is
case-insensitive, so they render identically to 'Geist'/'Geist Fallback'.)
Co-Authored-By: Claude Fable 5 <[email protected]>
The absolute panel overlaid the full-width centered chat column, hiding the
messages/composer behind it (WorkClaw avoids this by reserving the panel's
width as padding on the chat). Introduced a shared --computer-width var,
set once on the ChatWorkspace row per ?device= (phone 448px, tablet 67%,
full 100%; 0 when closed). Both the panel <aside> width and a new chat-
column wrapper's md:pr read it, so they never drift: opening the panel
squeezes the centered max-w-3xl column into the left space (re-centered)
while the panel overlays the reserved right area. Animated with
transition-[padding]/[width] + the signature easing for the 'condense'
effect. The header is outside the padded wrapper, so its cluster shift is
unaffected. Verified at 1440: composer right clears the panel left edge in
both tablet (561<593) and phone (920<992) — nothing hidden.
86 unit + 31 functional E2E green.
Co-Authored-By: Claude Fable 5 <[email protected]>
When the panel opened the cluster only shifted a fixed 168px, so the
Slack/Sessions/New icons stayed near the right instead of moving to the
middle. Now the open-state margin matches the panel width (phone 448px,
tablet/full 67%), so the cluster lands at the panel's LEFT edge — the
middle column beside the chat — mirroring WorkClaw's shrinking-pane move.
Animated with transition-[margin]. Verified at 1440: collapsed Computer
ends 1420 (flush right); tablet → 600 (panel left 593); phone → 972
(panel left 992). Device/Close overlay stays far-right; no overlap.
86 unit + 31 functional E2E green.
Co-Authored-By: Claude Fable 5 <[email protected]>
The cluster was absolute-centered (left-1/2 -translate-x-1/2), pinning it to
the header center. WorkClaw right-aligns it with ml-auto in normal flow.
Switched to: title back to min-w-0 flex-1, action cluster ml-auto in flow
(vertical centering via the header's items-center). Verified at 1440:
collapsed cluster is flush right (Computer ends at 1420 = px-5 from edge).
When the panel is open the cluster shifts left (md:mr-[168px]) so the
absolute device/Close overlay sits to its right with a clean gap (Computer
ends 1252, toggles 1296-1420) — no overlap.
86 unit + 31 functional E2E green.
Co-Authored-By: Claude Fable 5 <[email protected]>
Action cluster now Slack → Sessions → New → separator → Computer, chip-styled.
Full suite (37) green.
Co-Authored-By: Claude Fable 5 <[email protected]>
Match WorkClaw's collapsed action cluster. Left-to-right now reads
Slack → Sessions → New → │ separator │ → Computer (coral):
- New Slack quick-launch chip (brand /services/slack.svg) as the first
action, opening the Computer → Slack app via setParams({app:'slack'})
(same handler as the panel home tile; connect-gated there).
- Slack/Sessions/New wrapped in their own md+ sub-group and given the
raised-chip treatment (bg-neutral-900 + ring-1 ring-inset ring-white/8
+ shadow-bubble), preserving hover/active.
- Thin 24px vertical separator (w-px h-6 bg-white/12) before the coral
Computer toggle, which stays size-[42px]/shadow-launcher and always
visible (mobile-reachable; the action group hides <md per WorkClaw).
Handlers/aria-labels/icons of Sessions/New/Computer unchanged. The
header 'Slack' button doesn't collide with the panel-scoped Slack tests.
Verified live: cluster order Slack→Sessions→New→Computer; Slack opens
?app=slack.
86 unit + 31 functional E2E green.
Co-Authored-By: Claude Fable 5 <[email protected]>
Was a single right-aligned row where the title's flex-1 bunched the action
icons and the device controls together on the right. Now matches WorkClaw's
two-zone pattern:
- Identity stays left (capped width so it never reaches center).
- Action cluster (Sessions / New / Computer) is its own group, absolutely
centered in the header (measured at ~808, the chat-header center).
- Device-size toggles + Close are a separate, absolutely-positioned
top-right overlay (z-50, above the panel), shown only while the panel is
open. Toggles are md+ only; Close stays (now also reachable above the
mobile full-screen panel). Measured far-right at 1296–1420.
Button classes, icons, aria-labels and handlers are unchanged — only the
grouping/positioning wrappers differ. The panel card starts below the
header (pt-88), so both zones stay visible/clickable when the panel is open.
86 unit + 31 functional E2E green.
Co-Authored-By: Claude Fable 5 <[email protected]>
The panel is now a 847px (67%) absolute overlay at tablet, covering the
right of the chat which stays full-width underneath. Fresh capture; full
suite (37) green.
Co-Authored-By: Claude Fable 5 <[email protected]>
The previous pass left 'full' as an in-flow flex sibling, so it fought the
chat for layout space and overlapped (chat crushed to a sliver, panel
relative on top). Now the panel is an absolute right-anchored overlay in
EVERY state (never in-flow) — the chat keeps full width and never reflows;
opening the panel just covers the right N%:
- phone → md:w-[448px]
- tablet → md:w-[67%] (≈ the measured 847px at 1440)
- full → md:w-full (covers everything right of the rail)
Transition is now plain transition-[width] on the absolute aside.
Rail collapse in full mode: LeftRail reads ?device, and when the panel is
open in 'full' it collapses 176→80px, icon-only — labels/credits/user-text
hidden (md+) via a group/rail data-collapsed flag, animated in parallel
with the panel via transition-[transform,width]. Verified at 1440: tablet
panel=847 rail=176, phone=448, full panel=1360 rail=80; chat composer.left
stays 472 in phone/tablet (no shift). 24px card inset + 32px radius kept
throughout.
86 unit + 31 functional E2E green.
Co-Authored-By: Claude Fable 5 <[email protected]>
Rail logo (coral paw-print brand tile) and 58px agent tiles updated in
workspace-home, chat-welcome, computer-home (the last also shows the
panel as a right overlay — chat no longer shifts). Fixed the visual
signIn helper: it matched the login page's lowercase 'clawmates' heading
and could screenshot mid-navigation, so workspace-home captured the login
page — now it waits for navigation off /login and matches 'Clawmates'
exact. Avatar unit test updated for the 58px/rounded-20 rail tile.
Full suite (37) green.
Co-Authored-By: Claude Fable 5 <[email protected]>
PART 2 — panel positioning (the 'pushes the chat left' root cause):
DevicePanel was an in-flow flex sibling, so opening it consumed width and
shoved the chat. Now phone/tablet anchor it as an ABSOLUTE right-edge
overlay (md:absolute right-0 top-0 h-full z-30, w-[448px]/w-[550px]) so
the chat keeps full width and does NOT reflow; only 'full' stays in-flow
(grow-[4]) and lets the chat shrink. The chat row is now relative; the
aside is pointer-events-none with the screen card pointer-events-auto so
the header toggles/close stay clickable under the overlay's transparent
top padding. Verified live: composer.left is identical (472px) panel
open vs closed in tablet — the chat no longer shifts.
PART 1 — left rail:
- Logo in its own 80px header row as a 48px black rounded-2xl brand tile
with a coral PawPrint mark (our placeholder logo) + sr-only 'clawmates'.
- Agent tiles bumped to 58px (Avatar 'rail' size + rounded-[20px]
squircle), centered, gap-1, 2px coral active ring (rounded-[22px]).
- Hover-revealed ⋯ menu per agent row (new AgentRowMenu): a 176px #1A1A1A
popover with Pin + Settings rows (coral 15px icons, 14px labels), closes
on outside-click/Escape. Pin pins the claw to the top of the rail
(persisted in localStorage, client-side); Settings deep-links to that
claw's Computer → Settings (the claw redirect now forwards ?app=).
86 unit + 31 functional E2E green.
Co-Authored-By: Claude Fable 5 <[email protected]>
Composer centered in the welcome stack with attach/screenshot icons and
no Send, 12px subtitle, rounded 126px hero avatar, and the larger chat
header (58px avatar / 18px name / 14px role). Captured fresh; full suite
(37) green.
Co-Authored-By: Claude Fable 5 <[email protected]>
Measured welcome-screen + active-conversation feedback:
Composer redesigned with two variants (no Send button — Enter to send,
Shift+Enter for newline; attach + screenshot icon affordances on the
left). 'welcome' = short single-row 672px-capped pill, icons inline;
'active' = taller two-row card (textarea on top, 36px icon-button row
below), placeholder switches to 'Type your message…'. A cream Stop pill
appears while the agent is generating, wired to a real useChat.stop()
that aborts the SSE and finalizes the partial reply (new 'stopped'
reducer action).
Empty state un-pinned: the composer is now part of the centered welcome
stack (avatar → heading → subtitle → composer → chips) instead of being
pinned to the viewport bottom. Subtitle dropped to 12px. Hero avatar
kept at 126px but with a proportional 40px squircle radius (a flat 17px
read as square at that size).
Chat header sized up to match: 58px avatar (new Avatar 'header' size),
name 18px/600 neutral-200, role 14px. The neutral-500 role/subtitle tone
nudged to #7d7d7d to clear AA contrast on #0a0a0a.
84 unit + 31 functional E2E green; verified both states live.
Co-Authored-By: Claude Fable 5 <[email protected]>
Tablet-width card, size toggles + close in the global header, and the
enlarged dock (56px tiles / 34px glyphs / 11px labels). Captured from a
verified-fresh server; full suite (37) green.
Co-Authored-By: Claude Fable 5 <[email protected]>
Measured panel feedback — four fidelity gaps closed:
1. Size toggles + close moved OUT of the panel card header into the
global chat header (where the reference mounts them), beside a thin
divider. DeviceSizeToggle restyled to 28px p-1.5 rounded-md with
inactive at opacity-30, active at full. The panel card header now
carries only the home/back button + title + status dot.
2. Device-state widths wired (were stuck at 400px). DevicePanel now
manages its own complementary <aside> sized by flex per ?device=:
phone basis-[448px] (400 card), tablet basis-[550px] (502 card),
full basis-0 grow-[4] — fluid, fills the row beside the chat. Verified
live: aside measures 448 / 550 / 1011 at a 1440 viewport. Wrapper
px-3→px-6 (24px gutters); card is now w-full (width driven by aside).
3. The grow animates — transition-[flex-basis,flex-grow] with the
signature --duration-normal / ease-app on the aside.
4. Dock glyphs enlarged: tiles size-12→size-14 (56), glyph 22→34, plus
the 11px label under each tile.
Mount/unmount preserved via transitionend (flex-basis|flex-grow), as is
role=complementary aria-label=Computer, the device-panel-theme testid,
and the mobile full-screen overlay. p3/p4 updated to find the toggle +
close at page scope (now in the header, outside the panel region).
83 unit + 31 functional E2E green.
Co-Authored-By: Claude Fable 5 <[email protected]>
The previous baselines were captured against a stale next-start server
(Playwright reuseExistingServer reused a ghost on :3100 serving the
pre-fix bundle), so they encoded the broken render. Deleted and
regenerated from a verified-fresh server: chat-welcome (coral name, 2×2
pill chips, #1A1A1A composer), computer-home (real Chrome/Slack brand
tiles, coral Claw Chat glyph, frosted dock, icon size-toggle), credits,
marketing, team-orgchart. Full suite (37) green against them.
Co-Authored-By: Claude Fable 5 <[email protected]>
Side-by-side comparison surfaced that several restyle classes silently
generated NO CSS (confirmed against the compiled .next CSS) — invisible
to tests because unit tests assert class strings, not computed styles,
and visual baselines were captured from the broken build.
Root causes fixed:
- rounded-radius-button (51) / rounded-radius-squircle (3) generated
nothing → every pill/button/chip was border-radius:0. Tailwind v4 maps
--radius-button to 'rounded-button', not 'rounded-radius-button'.
Replaced with rounded-full / rounded-[17px].
- duration-normal/fast/slow (58) generated nothing → transitions had
easing but 0 duration (snapped). Replaced with the token-arbitrary
form duration-(--duration-normal). (Also fixed an over-replace that
double-wrapped var(--duration-normal) inside animate-[…] values.)
- HomeScreen.tsx was the OLD emoji version — the R3 rewrite was never
committed. Restored: brand Chrome/Slack squircle tiles, coral
GradientGlyph icons, 56px tiles with press-scale, and the frosted
glass dock (bg-white/[0.07] backdrop-blur-[40px] saturate-150).
Targeted gaps from the feedback:
- raised surface --color-surface-warm-muted #1f1f1f → #1a1a1a (composer,
chips, tabs)
- welcome chips: max-w-[422px] gap-3 → centered 2×2 grid
- composer input + rail nav labels → 14px
- DeviceSizeToggle: Full/Tablet/Phone text → lucide monitor/tablet/
smartphone icons (aria-label keeps the radio names)
- SW cache bumped v1→v2 so redeployed clients purge the stale bundle
- LeftRail drawer: ref-during-render → prev-state pattern (lint)
Compiled CSS now emits .rounded-full, transition-duration:var(--duration-
normal), border-radius:17px, and shadow-dock-capsule. Button/Avatar unit
tests updated to the real class names. 83 unit tests green.
Co-Authored-By: Claude Fable 5 <[email protected]>
- Regenerated all 7 @visual darwin baselines for the restyled surfaces
(login, workspace home, chat welcome, computer panel, credits) and
added two new ones: the marketing landing (full page) and the Team
org-chart tab.
- Credits visual made deterministic: balance + the whole usage card are
masked (testids), and the runway line always renders so the card
height is constant — shared-backend credit spend during the full run
no longer shifts the diff.
- Full gate sweep green end to end: 173 Rust tests + clippy/fmt clean,
83 frontend unit + lint + typecheck, all 37 Playwright journeys
(functional + visual), axe serious/critical at zero across every
surface, LOC ≤1250 and no-placeholder gates.
The interface now matches the reference design system across the app,
the Computer panel, the global pages, and a new light-theme marketing
site — design system only, our brand throughout.
Co-Authored-By: Claude Fable 5 <[email protected]>
- SlidePanel gains an prop: at ≤md the open Computer panel
becomes a fixed full-screen layer (max-md:!w-full beats the inline
width) instead of a cramped docked rail — the measured mobile/tablet
behavior. The screen card goes full-bleed (no radius/shadow/max-width)
on small screens; desktop docking is unchanged.
- LeftRail collapses behind a hamburger at ≤md and slides in as a drawer
over the content with a backdrop, auto-closing on navigation
(render-phase route check, no effect). Desktop keeps the static 176px
rail (the hamburger and drawer transforms are max-md-scoped, so the
1440 desktop suite is untouched).
83 unit tests; desktop p0/p3/a11y journeys green; verified at 390px the
rail collapses to the toggle.
Co-Authored-By: Claude Fable 5 <[email protected]>
A new (marketing) route group with its own LIGHT layout (slate palette +
marketing coral #E95656 + periwinkle cards on near-white), the inverse of
the dark app. The middleware rewrites '/' → /marketing for logged-out
visitors (keyed on the session cookie / Clerk __session); authed users
fall through to the workspace app unchanged.
Built to the measured marketing system with OUR copy and honest claims —
NO SOC 2 / compliance badges: floating white pill nav, the 75px/800/-3px
display hero with a single coral keyword span, dual pill CTAs (coral
primary / white-bordered secondary), trust badges ('free credits',
'no credit card', 'self-hostable & air-gapped'), periwinkle feature
cards, a structural-security section (approval-gated, broker-held creds,
air-gappable — our real differentiators), an interactive FAQ accordion,
CTA band, and footer.
E2E (p7-marketing): renders for logged-out visitors, FAQ accordion works,
asserts NO SOC 2 claim, and an axe pass (coral CTAs sized to clear the
AA large-text threshold). p0-shell updated: logged-out '/' now shows
marketing with a Sign-in link to /login.
Co-Authored-By: Claude Fable 5 <[email protected]>
Shared PageChrome (28px/600 title + muted desc + top-right action pill)
now fronts every global page. Skills → 2-col r24 Card grid with team
install counts. Credits → three-card layout (balance w/ Buy credits;
usage meter w/ runway; promo; Talk-to-sales → mailto). The Stripe Buy
credits button only mounts when /api/billing/config reports it enabled
(honest degradation) and opens a real Checkout Session.
Team page gains the three reference tabs via SegmentedTabs: Members
(restyled), Claw org chart (real /api/team/orgchart — members grouped
with the claws they manage, each a deep link into chat), and Leaderboard
(real /api/team/leaderboard — claws ranked by usage with a coral bar).
New /apps global page (workspace-wide connections via ?workspace=true):
category pills + SearchPill + 2-col rows with inline API-key connect;
Apps added to the rail nav.
Wizard restyled to the system: coral-fill white-text CTAs with the glow
shadow, coral progress bars, swatch enter animation, system inputs —
all step text/behavior preserved.
83 unit + 29 functional E2E + a11y green; contrast fixed (subtle-fg →
muted-fg on cards).
Co-Authored-By: Claude Fable 5 <[email protected]>
Panel chrome (CONSOLIDATED-computer-panel-handoff): the 448px docked rail
now hosts a 400×652 r32 'screen' card with the measured wallpaper stack —
135° slate/navy base gradient tinted by the agent accent, a #000/.40
vignette, and a frosted blur(40px) white/5 overlay with grain on top.
Header carries the rose-500 status dot (home) or a back chevron (drilled),
keeping the 'Computer home' / 'Close computer' accessible names stable.
Home screen: 56px tiles with 16px-radius icon containers (Chrome/Slack
brand marks on white squircles, coral-gradient lucide glyphs elsewhere),
11px labels, active:scale-[0.92] press; the frosted dock capsule uses the
exact blur(40px) saturate(1.5) bg-white/7 r24 + inset/drop shadow with
48px black tiles.
App window choreography: opening an app zooms it out of the tapped tile
(transform-origin + --app-origin-scale captured at click time → the
app-shell-zoom-in keyframe), ~0.28s ease-app.
All 8 apps restyled to the inventory spec via shared AppShell helpers
(SubViewHeader, PanelEmptyState = muted lucide glyph + 16/600 + 12 muted):
Files 45px rows w/ folder/file/chevron glyphs; Slack segmented tabs
(active white bg / aubergine text) + cream connect gate; Skills pinned
cream 'Add Skill'; Add Apps category pills + SearchPill + coral connect;
Settings grouped r12 cards + coral radios + cream Save + lucide delete;
Browser/Routines/ClawChat lucide empty states. Every test marker text and
aria-label preserved.
83 unit + 29 functional E2E green; lucide icons throughout (emoji gone).
Co-Authored-By: Claude Fable 5 <[email protected]>
Shell (main-chat-shell-spec): rail rebuilt around a 48px squircle claw
avatar stack with a 2px coral active ring + name beneath + online dot,
dashed 'New claw' tile, lucide-icon nav pills with the live credits
balance shown inline (coral when negative, aria-hidden so the nav link
name stays 'Credits'); 80px transparent chat header with 36px round
sessions/new-session icon buttons and the 42px coral-glow Computer
launcher; 768px centered content rail. RosterList computes the active
claw from the pathname so AgentRosterItem stays presentational.
Chat (chat-message-components): asymmetric layout — user pill #1A1A1A
radius 24/24/4 with inset white ring + dual shadow, capped 75%, vs the
bubble-less assistant message (50px squircle avatar, 15px gap, plain
14px/1.7 text); messageSlideIn entrance; 126px welcome avatar with the
24px/600/-0.6px heading (claw name in coral) and lucide-led suggestion
chips; floating 24px-radius neutral-800 composer card with the cream
Send pill; StepTrace rows restyled to the system.
Brand marks (Chrome, Slack) vendored to public/services from the bundle.
83 unit tests, full 29-journey functional E2E green.
Co-Authored-By: Claude Fable 5 <[email protected]>
- scripts/netpol-cluster.sh: a kind cluster with Calico (default CNI
disabled) — the only way to PROVE the §15 default-deny NetworkPolicy,
since kindnet accepts the object but never enforces it. New live test
on that cluster: outbound connect to 1.1.1.1 dropped, DNS egress
dropped, while API-server exec keeps working (not pod network).
Kernel-level enforcement of the sandbox egress claim, demonstrated
- K8sDriver::connect_with_context: pin a kubeconfig context instead of
ambient. The whole k8s suite now pins its cluster explicitly — the
netpol cluster's creation had silently switched the current context
and stranded the seccomp test on the wrong cluster (fixed and made
impossible to recur)
- CI: netpol-cluster up + calico egress test in the sandbox-k8s job
- Visual-regression lock (plan P6): @visual Playwright spec with
animation-disabled, masked-dynamic-region screenshots of login,
workspace home, chat welcome, computer panel, credits; darwin
baselines committed (5 PNGs); CI excludes @visual until linux
baselines are generated there. Full local suite: 33 journeys
165 Rust tests + 5 live kind tests (2 clusters) + 33 journeys.
Co-Authored-By: Claude Fable 5 <[email protected]>
Full-depth rename per the approved plan; the 'claw' product vocabulary
(claws, /claws routes, clawId, Claw Chat) stays — it is now the brand.
- Display brand: Clawmates (manifest, titles, hero, login/rail logo
'clawmates'); default host app.clawmates.work; registry
ghcr.io/clawmates
- Crates tc-* -> cm-* (16 crates + all imports); binaries
clawmates-server/broker/bundler; images clawmates/*; env prefix
CLAWMATES_* (+ CM_TEST_DATABASE_URL / CM_LIVE_LLM); config
clawmates.toml; helm chart deploy/helm/clawmates with clawmates-*
resources; db names clawmates*; sockets /run/clawmates; cookie
cm_session; kind cluster clawmates-test; seccomp node profile
clawmates-agent-profile.json
- All 9 Playwright brand assertions updated in lockstep; historical
spec document left untouched as the only remaining 'TeamClaw'
- Local env migrated: dev pg clawmates-dev-pg/clawmates_dev, shared
test server clawmates-test-pg, kind cluster recreated with image +
profile, compose images rebuilt under clawmates/*
Verified end to end: 161 Rust + 68 frontend tests, 29 Playwright
journeys, 4 live kind tests, helm/install/LOC/placeholder gates, and
the clean-room install rehearsal serving the clawmates login page from
a signed bundle of the rebuilt images.
Co-Authored-By: Claude Fable 5 <[email protected]>
- src/lib/auth/bearer.ts is the single identity dispatch for both
server-side token consumers (RSC apiFetch and the /api proxy route):
local -> httpOnly tc_session cookie; clerk -> Clerk getToken() session
JWT. The Clerk SDK is imported lazily, so the air-gapped/local path
never loads it
- Runtime env (AUTH_MODE / CLERK_PUBLISHABLE_KEY / CLERK_SECRET_KEY),
deliberately NOT build-time NEXT_PUBLIC_*: the same standalone image
serves both deployment targets
- Conditional <ClerkProvider> in the root layout (publishableKey passed
at render from runtime env); /login renders Clerk's <SignIn /> in
clerk mode and the local form otherwise; proxy.ts middleware delegates
to clerkMiddleware() only when active
- Helm: frontend deployment injects the Clerk keys from a Secret when
auth.mode=clerk
- mode.ts unit-tested (default local, exact-match clerk, loud failure
without the publishable key); the local path stays proven by all 29
journeys; the Clerk branch is thin delegation to the SDK, exercised in
deployment smoke per docs/clerk.md
157 Rust + 68 frontend tests + 29 Playwright journeys.
Co-Authored-By: Claude Fable 5 <[email protected]>
- PWA (§16): hand-rolled 60-line service worker (network-first pages with
offline fallback, cache-first hashed statics, /api NEVER touched — SSE
and approvals stay live), app manifest with §2 identity, stdlib-
generated coral claw icons, prod-only registration. E2E asserts
manifest, real PNG icons, an ACTIVATED service worker, and the /api
bypass. (Serwist was tried and dropped: its webpack plugin fights
Next 16's Turbopack builds; sixty lines we own beat a plugin we fight.)
- Route motion (§3): (workspace) template re-mounts per navigation with a
quiet fade-rise, zeroed under prefers-reduced-motion. The a11y sweep
now settles running animations before scanning — axe was reading
mid-fade opacity as contrast failures
- OAuth browser flow vs REAL dex: the e2e harness boots dexidp/dex with
static client + password; the journey drives the actual dex login form
from /api/apps/oauth/start through the callback 303 and asserts the
app reads connected (closing the P4 deferral honestly)
- release.yml: tag-triggered — builds all four images + postgres, saves
tarballs, assembles the SIGNED air-gapped bundle (compose, config,
migrations, seccomp profile, installer, bundler binary), derives the
public key via the new Could not find command "pubkey". subcommand (tested), verifies
the bundle customer-style with the public half only, attaches tarball
+ public key to the GitHub release
153 Rust + 63 frontend tests + 29 Playwright journeys.
Co-Authored-By: Claude Fable 5 <[email protected]>
- SandboxSpec gains an egress flag (default false — the kernel suite
still proves zero-network for agent sandboxes). Egress-enabled
containers exist ONLY for the browser: no credentials, no broker
route, bridge network with host-gateway alias for local test pages
- images/agent-browser: Alpine Chromium, uid 10001, setuid bits
stripped — same non-root hardening as agent-base
- browser.goto tool: headless chromium --dump-dom in the agent's
browser container; HTML stripped to readable text (4k cap) and
returned with output_taint=web; viewport screenshot captured,
base64'd out of the container, stored in the blob store
- Taint semantics tightened: the step that PRODUCED untrusted output
now carries its own taint (recorded before the step row), not just
later steps — chat.inbox test updated to the stricter §15 reading
- GET /api/claws/{id}/browser/viewport.png serves the latest capture;
BrowserApp polls it and renders the live viewport (spec §7.1),
keeping the empty state until the agent has browsed
- Proven end to end with REAL Chromium against a REAL local page:
content 'Revenue up 14 percent' returned tainted web; the gated
email.send that follows carries 'web' in its approval taint_sources
(untrusted content can never quietly reach outward); screenshot
verified by PNG magic bytes
152 Rust tests + 63 frontend + 27 Playwright journeys.
Co-Authored-By: Claude Fable 5 <[email protected]>
- Concurrency soak (exit criterion): 12 concurrent gated runs, every
decision attempted twice concurrently, explicit resumes racing the
durable sweeper — exactly one execution per approval, grants consumed
at most once, every decision audited, zero stuck runs, zero unaudited
executions. (Testkit pool raised to 20 connections; the 5-connection
pool starved the storm.)
- axe a11y sweep (exit criterion): serious+critical violations fail CI on
login, shell, chat, computer home, settings app, all global pages, and
the wizard. Two real violations found and fixed: aria-label on a plain
div (wizard progress -> role=group) and a button directly inside a <dl>
(settings -> plain bordered list).
- tools/bundler (exit criterion): keygen / assemble / verify CLI — copies
artifacts, writes manifest.json + sha256 checksums.txt + a detached
ed25519 signature; verification is fully offline (keyless signing is
internet-dependent and disqualified). Tests: round trip, tampered
artifact caught by hash, tampered checksum list caught by signature,
wrong key refused, missing artifact reported.
147 Rust + 63 frontend tests + 27 Playwright journeys (incl. 4 a11y).
Co-Authored-By: Claude Fable 5 <[email protected]>
- LlmEvent::Usage across all three providers (Scripted deterministic
word-count accounting; Anthropic message_start/delta usage; OpenAI-compat
stream_options include_usage)
- tc-billing: ceil(tokens/1000) min 1 credit; lots drain oldest-first under
FOR UPDATE; balance clamps at zero while the usage ledger records the
full obligation; promo codes redeem exactly once via CAS (migration 0006)
- Runtime charges every completed run (billing failure never fails a run);
proven: 1 token in + 3 out -> 1 credit deducted
- API: GET /api/team/usage, POST /api/credits/redeem (409 on reuse, audited)
- Credits page: balance, 7-day usage meter with runway estimate, PromoRedeem
- /claws/new is the full §9 wizard: ?step=identity|access|slack deep-linked
progress, accent swatches + name randomizer, access toggles, optional
Slack step, explicit review-and-confirm (creation = live agent), animated
provisioning state -> straight into chat
- E2E: chat decrements the visible balance and fills the usage meter;
WELCOME500 adds exactly 500 once then refuses; wizard round trip
140 Rust + 63 frontend tests + 23 Playwright journeys.
Co-Authored-By: Claude Fable 5 <[email protected]>
- migration 0005 oauth_states: one-time states (10-min TTL), consumed by a
CAS DELETE on callback — replays and forgeries both 404
- POST /api/apps/oauth/start: OIDC discovery on the configured issuer (or
the custom MCP issuer for authType=mcp_oauth), state row, authorize URL
- GET /api/apps/oauth/callback: code exchanged at the REAL token endpoint
(client id+secret form POST); the access token goes straight to the
broker (test proves it never appears unencrypted in Postgres); connection
row + audit; redirects to the claw's Add Apps panel
- [oauth] config (issuer/client/redirect_base) wired through AppState
- Tests against a real local IdP server (discovery + validating token
endpoint): full round trip, broker-held token, replay/forged state
refused, bad code fails exchange, mcp_oauth uses the custom issuer while
plain oauth refuses without a configured IdP
- AddAppsApp: live connection badges + inline API-key connect per app
(E2E: connect Notion by key from the directory)
136 Rust + 63 frontend tests + 21 Playwright journeys.
Co-Authored-By: Claude Fable 5 <[email protected]>