mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 18:31:34 +01:00
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - First-run onboarding is the subsystem that turns a brand-new install
into a working company: it creates the company, its goal, a lead agent,
and that agent's first task
> - The existing `OnboardingWizard` carried all of that wiring
correctly, but its UI had drifted from the current design direction, and
a separate design prototype (`paperclip-onboard`) existed as a
standalone visual mock with no backend
> - Porting the prototype's *logic* would have thrown away working,
well-tested backend orchestration; leaving the two apart meant the
design never shipped
> - Separately, cloud and local (self-hosted) installs need meaningfully
different first runs — local has no sign-in and must let the user pick a
locally-installed CLI adapter — so a single linear wizard could not
serve both
> - This pull request rebuilds the presentational layer from the
prototype on top of the existing backend orchestration, and splits it
into two thin flow containers over a shared core
> - The benefit is that the shipped onboarding matches the intended
design, cloud and local can diverge without duplicating logic, and each
can later ship to a different app version while sharing one set of step
components
## Linked Issues or Issue Description
No existing issue — describing inline (feature request).
**What problem does this solve?**
Onboarding is the first thing a new user sees, and the shipped wizard
had drifted from the current design. In parallel, cloud and local
installs need different first-run paths: local has no hosted sign-in,
and its agent runs on a CLI adapter installed on the user's machine,
which the cloud path never has to ask about. There was no way to express
that difference without either forking the whole wizard or bolting
conditionals onto a single linear flow.
**Proposed solution**
Extract the onboarding step views and shell into a shared core, then
compose two thin flow containers (cloud and local) over it. Keep all
backend orchestration in the existing `useOnboardingFlow` hook so no
working logic is rewritten.
**Alternatives considered**
- *Single flow with a `variant` prop* — most DRY, but the two flows are
intended to ship on different app versions, and a shared file would have
to be split later anyway.
- *Two fully independent copies* — simplest per-flow, but every shared
refinement (spacing, motion, copy) would have to be made twice and would
drift.
## What Changed
- **Shared core** under `ui/src/components/onboarding/`:
`OnboardingScaffold` owns the full-screen shell and the single
`AnimatePresence` step crossfade, so both flows transition identically;
step views (Start / Company / Agent / Task), `FooterNav`, `AgentPreview`
and the motion constants are extracted for reuse.
- **`CloudOnboardingFlow`** — `start → company → agent → task`; mounted
in the real app via `OnboardingWizardVariant`. Behaviour matches the
retired wizard, including `previewMock` and the existing-company ("add
an agent") entry point.
- **`LocalOnboardingFlow`** — skips sign-in and adds an optional email
ask (with a privacy assurance), a local model/adapter step that hires
with `requireEnvProbe: true`, and a "star us on GitHub" interstitial
before completing. **Harness-only for now** — the real app still mounts
the cloud flow.
- **Deleted `OnboardingWizard.tsx`** (1,786 lines); updated its
Storybook stories and the `OnboardingWizardVariant` test to the new
components.
- **Orbiting 3D paperclip backdrop** behind the auth and welcome screens
(`three`), code-split so it only downloads on those screens; honours
`prefers-reduced-motion` and disposes its GL context on unmount.
- **`motion`** added for step transitions and the agent-capsule
choreography.
- Visual values routed through design tokens per `DESIGN.md`; `Stepper`
generalized to take a step total (backward compatible); `/design-guide`
page and the component index updated.
- **Standalone preview harness** (`ui/onboarding-preview.html`) with
`?flow=` and `?step=` for backend-free review, wired as a second Vite
rollup input.
- **Adapter env probe bound to the adapter it ran against.**
`hireLeadAgent` reused `adapterEnvResult` for any adapter, so when a
hire failed and the user picked a *different* local adapter and retried,
the previous adapter's verdict satisfied the `requireEnvProbe` guard
while the hire posted the new adapter's config — hiring it unprobed. The
cache is now keyed on the adapter type plus the exact config posted to
the test endpoint, the config is built once and shared by probe and
hire, a failed probe clears the cache, and `clearAdapterEnvResult()`
(called on adapter change) stops the step displaying a stale verdict.
Cloud is unaffected — it hires with `requireEnvProbe: false`. Reported
by Greptile.
- **E2E specs re-pointed at the new flow.** Four specs still drove the
deleted wizard (`onboarding`, `conference-room-typing-intro`,
`planning-mode-visual-verification`, `nux-phase4-screenshots`) and
failed with `element(s) not found` on `"Name your company"` /
`input[placeholder="Acme Corp"]`. Rather than repeat the new drive
sequence four times, `tests/e2e/onboarding-flow.ts` adds one driver per
step (`startCloudOnboarding`, `completeCompanyStep`,
`completeAgentStep`, `completeTaskStep`, `completeCloudOnboarding`) and
the specs import it, so the next flow change touches a single file. Two
now-dead `**/test-environment` route stubs went with it — the cloud flow
hires with `requireEnvProbe: false`, so that probe never fires.
## Verification
- `pnpm --filter @paperclipai/ui typecheck` — clean.
- `npx vitest run` over the onboarding suites
(`OnboardingWizardVariant`, `AgentCapsule`, `onboarding-launch`,
`onboarding-goal`, `onboarding-route`, `onboarding-adapter-config`) — 33
tests pass.
- `pnpm --filter @paperclipai/ui build` — succeeds; the three.js chunk
splits out separately (522 kB raw / 133 kB gzip) rather than entering
the main bundle.
- Both flows driven end-to-end in the preview harness in `previewMock`
(no database writes), plus the cloud flow rendered in the real
authenticated app at `/onboarding` to confirm the mount swap.
- The four re-pointed e2e specs pass locally against the new flow.
- New `ui/src/hooks/useOnboardingFlow.test.tsx` — 4 cases pinning the
adapter-probe cache (switch-adapter retry, cold path, explicit clear,
and the cloud flow's `requireEnvProbe: false`). Verified non-vacuous:
the switch-adapter case fails against the pre-fix code.
- Rebased onto current `master`; `pnpm-lock.yaml` is deliberately
**not** committed — `.github/workflows/pr.yml` regenerates it when a
manifest changes and shares it with downstream jobs as the `pr-lockfile`
artifact.
## Risks
- **Deleting `OnboardingWizard.tsx` is the one change that alters
existing app behaviour.** The cloud flow is intended to be
behaviour-equivalent, and its entry points are covered by the updated
`OnboardingWizardVariant` test, but this is the area to review most
closely.
- **Conflict risk with open PRs that touch the old wizard**: #9900,
#9501, #8982 and #6636 all modify
`ui/src/components/OnboardingWizard.tsx`, which this PR removes.
Whichever lands second will need its change re-applied to the new step
components. Flagging so ordering can be decided deliberately.
- **New dependencies**: `motion` and `three` (+ `@types/three`). `three`
is large, so it is lazily imported and code-split — it does not affect
the main bundle. Both are MIT.
- The **local flow is not reachable in the app** yet (harness/canary
only), so it carries no runtime risk today; wiring it up is a follow-up.
- The auth screens remain **presentational only** — they are not wired
to real auth, unchanged from before this PR.
- **Pre-existing, not introduced here:** `OnboardingWizardVariant`
renders outside `<Routes>` in `App.tsx`, so its `useParams()` never
resolves `:companyPrefix` and `/{prefix}/onboarding` opens the welcome
screen instead of jumping to the agent step. `master` has the identical
structure, so this PR faithfully ports existing behaviour; the working
"add an agent" entry is the launcher card behind the overlay, which is
what the screenshot spec drives. Worth a separate fix.
## Model Used
Claude Opus 5 (`claude-opus-5`) via Claude Code, with extended thinking
and tool use (repo search/edit, local test + build execution, and
browser-driven visual verification of the rendered flows). Portions of
the session also ran on `claude-opus-4-8` and `claude-fable-5`.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2388 lines
110 KiB
CSS
2388 lines
110 KiB
CSS
@import "tailwindcss";
|
||
@plugin "@tailwindcss/typography";
|
||
|
||
@font-face {
|
||
font-family: "InterVariable";
|
||
src: url("../fonts/InterVariable.woff2") format("woff2");
|
||
font-display: swap;
|
||
font-style: normal;
|
||
font-weight: 100 900;
|
||
}
|
||
|
||
@font-face {
|
||
font-family: "InterVariable";
|
||
src: url("../fonts/InterVariable-Italic.woff2") format("woff2");
|
||
font-display: swap;
|
||
font-style: italic;
|
||
font-weight: 100 900;
|
||
}
|
||
|
||
@custom-variant dark (&:is(.dark *));
|
||
|
||
@theme inline {
|
||
--font-sans: "InterVariable", "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||
--color-background: var(--background);
|
||
--color-foreground: var(--foreground);
|
||
--color-card: var(--card);
|
||
--color-card-foreground: var(--card-foreground);
|
||
--color-popover: var(--popover);
|
||
--color-popover-foreground: var(--popover-foreground);
|
||
--color-primary: var(--primary);
|
||
--color-primary-foreground: var(--primary-foreground);
|
||
--color-secondary: var(--secondary);
|
||
--color-secondary-foreground: var(--secondary-foreground);
|
||
--color-muted: var(--muted);
|
||
--color-muted-foreground: var(--muted-foreground);
|
||
--color-accent: var(--accent);
|
||
--color-accent-foreground: var(--accent-foreground);
|
||
--color-destructive: var(--destructive);
|
||
--color-destructive-foreground: var(--destructive-foreground);
|
||
--color-border: var(--border);
|
||
--color-input: var(--input);
|
||
--color-ring: var(--ring);
|
||
--color-chart-1: var(--chart-1);
|
||
--color-chart-2: var(--chart-2);
|
||
--color-chart-3: var(--chart-3);
|
||
--color-chart-4: var(--chart-4);
|
||
--color-chart-5: var(--chart-5);
|
||
--color-sidebar: var(--sidebar);
|
||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||
--color-sidebar-primary: var(--sidebar-primary);
|
||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||
--color-sidebar-accent: var(--sidebar-accent);
|
||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||
--color-sidebar-border: var(--sidebar-border);
|
||
--color-sidebar-ring: var(--sidebar-ring);
|
||
/* Multiplicative radius ladder (modern shadcn/Tailwind v4 mapping):
|
||
sm/md/lg/xl/2xl/3xl/4xl = 0.6/0.8/1.0/1.4/1.8/2.2/2.6 x the single
|
||
--radius knob (base anchor 0.5rem = 8px; rounded-lg IS the anchor).
|
||
Replaces the additive +/-px ladder (DECISION-SHEET B4 → Run 3 review):
|
||
retune the whole app's corner language by editing --radius alone. */
|
||
--radius-sm: calc(var(--radius) * 0.6);
|
||
--radius-md: calc(var(--radius) * 0.8);
|
||
--radius-lg: var(--radius);
|
||
--radius-xl: calc(var(--radius) * 1.4);
|
||
--font-heading: var(--font-sans);
|
||
--radius-2xl: calc(var(--radius) * 1.8);
|
||
--radius-3xl: calc(var(--radius) * 2.2);
|
||
--radius-4xl: calc(var(--radius) * 2.6);
|
||
}
|
||
|
||
:root {
|
||
color-scheme: light;
|
||
--radius: 0.5rem;
|
||
--background: oklch(1 0 0);
|
||
--foreground: oklch(0.145 0 0);
|
||
--card: oklch(1 0 0);
|
||
--card-foreground: oklch(0.145 0 0);
|
||
--popover: oklch(1 0 0);
|
||
--popover-foreground: oklch(0.145 0 0);
|
||
--primary: oklch(0.205 0 0);
|
||
--primary-foreground: oklch(0.985 0 0);
|
||
--secondary: oklch(0.97 0 0);
|
||
--secondary-foreground: oklch(0.205 0 0);
|
||
--muted: oklch(0.97 0 0);
|
||
--muted-foreground: oklch(0.556 0 0);
|
||
--accent: oklch(0.97 0 0);
|
||
--accent-foreground: oklch(0.205 0 0);
|
||
--destructive: oklch(0.577 0.245 27.325);
|
||
--destructive-foreground: oklch(0.577 0.245 27.325);
|
||
--border: oklch(0.922 0 0);
|
||
--input: oklch(0.922 0 0);
|
||
--ring: oklch(0.708 0 0);
|
||
--chart-1: oklch(0.87 0 0);
|
||
--chart-2: oklch(0.556 0 0);
|
||
--chart-3: oklch(0.439 0 0);
|
||
--chart-4: oklch(0.371 0 0);
|
||
--chart-5: oklch(0.269 0 0);
|
||
--sidebar: oklch(0.985 0 0);
|
||
--sidebar-foreground: oklch(0.145 0 0);
|
||
--sidebar-primary: oklch(0.205 0 0);
|
||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||
--sidebar-accent: oklch(0.97 0 0);
|
||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||
--sidebar-border: oklch(0.922 0 0);
|
||
--sidebar-ring: oklch(0.708 0 0);
|
||
--chip-match-title-bg: oklch(0.97 0.02 265);
|
||
--chip-match-title-fg: oklch(0.4 0.13 265);
|
||
--chip-match-title-border: oklch(0.85 0.05 265);
|
||
--chip-match-comment-bg: oklch(0.97 0.02 145);
|
||
--chip-match-comment-fg: oklch(0.4 0.10 145);
|
||
--chip-match-comment-border: oklch(0.85 0.05 145);
|
||
--chip-match-document-bg: oklch(0.97 0.02 295);
|
||
--chip-match-document-fg: oklch(0.4 0.10 295);
|
||
--chip-match-document-border: oklch(0.85 0.05 295);
|
||
--chip-match-identifier-bg: var(--muted);
|
||
--chip-match-identifier-fg: var(--muted-foreground);
|
||
--chip-match-identifier-border: var(--border);
|
||
--paperclip-doc-annotation-highlight-open: #fef08a;
|
||
--paperclip-doc-annotation-highlight-focused: #fde047;
|
||
--paperclip-doc-annotation-highlight-stale: #fef08a;
|
||
--paperclip-doc-annotation-highlight-resolved: #fef9c3;
|
||
|
||
/* Brand agent-capsule gradient stops (paperclip.ing/brand/).
|
||
"The capsule is the agent" — each agent gets a 2-stop gradient,
|
||
--agent-Na (top) → --agent-Nb (bottom). Brand-fixed hex, identical
|
||
across themes. Indices 1–10; consumed by AgentCapsule. */
|
||
--agent-1a: #f7cfdc;
|
||
--agent-1b: #1f7a3a;
|
||
--agent-2a: #c9a9e8;
|
||
--agent-2b: #ee79a1;
|
||
--agent-3a: #28164b;
|
||
--agent-3b: #7a1530;
|
||
--agent-4a: #f3e6c4;
|
||
--agent-4b: #e3a21a;
|
||
--agent-5a: #1f4dd6;
|
||
--agent-5b: #3aa35c;
|
||
--agent-6a: #e94b27;
|
||
--agent-6b: #5a1122;
|
||
--agent-7a: #7eb6e3;
|
||
--agent-7b: #ee79a1;
|
||
--agent-8a: #9ce8a7;
|
||
--agent-8b: #bd7ff0;
|
||
--agent-9a: #f3b49e;
|
||
--agent-9b: #1f4ed4;
|
||
--agent-10a: #f2d95f;
|
||
--agent-10b: #4fbcba;
|
||
|
||
/* Status colors — one base hue per status, consumed by the agent / task
|
||
status chips and the task status icons. Mode-independent: the `.status-chip`,
|
||
`.status-fill` and inline icon helpers derive the light vs dark
|
||
fill/text/border from these via color-mix, so a single hue covers both
|
||
modes. Values mirror the PAP-75 brand palette (gray inert, blue liveness,
|
||
amber queued, violet review, green done, red blocked). */
|
||
--status-agent-idle: #a8aeb2;
|
||
--status-agent-running: #2563eb;
|
||
--status-agent-paused: #f59e0b;
|
||
--status-agent-error: #dc2626;
|
||
--status-task-backlog: #a8aeb2;
|
||
--status-task-todo: #f59e0b;
|
||
--status-task-in_progress: #2563eb;
|
||
--status-task-in_review: #7c3aed;
|
||
--status-task-done: #22c55e;
|
||
--status-task-blocked: #dc2626;
|
||
--status-task-cancelled: #a8aeb2;
|
||
|
||
/* Task status ICON hues (PAP-238 3b) — AA-tuned (every glyph ≥ 3:1 against
|
||
its surface, WCAG 2.1 AA, both modes). Distinct from the
|
||
`--status-task-*` chip base hues: a status chip carries its own bg/text
|
||
tint, but a bare glyph sitting next to text on the page needs a stronger
|
||
hue to clear 3:1. Where the chip base already passes (blue liveness, red
|
||
blocked, light violet review) the icon var simply re-points at it; the
|
||
gray / amber / green statuses are pinned to the board-approved AA hexes.
|
||
`in_queue` is the BLOCKED shape recoloured to the in_progress blue
|
||
(replaces the bespoke teal "covered" state). Light values here; the
|
||
`.dark` block below overrides the four that need a mode-specific hue. */
|
||
--status-task-icon-backlog: #52585d; /* gray darkened — 7.21:1 on paper */
|
||
--status-task-icon-todo: #cc7a00; /* amber darkened — clears 3:1 */
|
||
--status-task-icon-in_progress: var(--status-task-in_progress); /* #2563eb both modes */
|
||
--status-task-icon-in_review: var(--status-task-in_review); /* #7c3aed (light) */
|
||
--status-task-icon-done: #16a34a; /* green darkened — clears 3:1 */
|
||
--status-task-icon-blocked: var(--status-task-blocked); /* #dc2626 both modes */
|
||
--status-task-icon-cancelled: #52585d;
|
||
--status-task-icon-in_queue: var(--status-task-in_progress); /* blocked shape, blue */
|
||
|
||
--folder-color-indigo: #6366f1;
|
||
--folder-color-violet: #8b5cf6;
|
||
--folder-color-emerald: #10b981;
|
||
--folder-color-cyan: #06b6d4;
|
||
--folder-color-amber: #f59e0b;
|
||
--folder-color-slate: #64748b;
|
||
--gtc-folder-row: 1rem minmax(0, 1fr) auto;
|
||
--gtc-folder-row-actions: 1rem minmax(0, 1fr) auto auto;
|
||
--sz-folder-rail: 13.25rem;
|
||
--sz-folder-sheet-max: 80dvh;
|
||
--sz-tweak-panel-max: 70vh; /* Task Chat Redesign dev tweak panel scroll cap. */
|
||
|
||
/* ────────────────────────────────────────────────────────────────────────
|
||
Motion tokens — Task Chat Redesign (flag: enableTaskChatRedesign).
|
||
|
||
These are the single source of truth for the redesigned thread's motion.
|
||
Kept in :root (NOT @theme inline) on purpose: @theme inline bakes literals
|
||
at build time, so the dev tweak panel — which writes these live via
|
||
element.style.setProperty — could not move a baked value. Rationale for
|
||
each transition point lives in DESIGN.md; VALUES here are tunable
|
||
placeholders the human tunes live via the tweak panel, then pastes back.
|
||
Reuses the two house easing curves already used across the app. Any new
|
||
motion in the redesigned thread MUST reference one of these tokens — no
|
||
hardcoded ms / cubic-bezier in components (enforced by a check script).
|
||
──────────────────────────────────────────────────────────────────────── */
|
||
|
||
/* Easing primitives (tunable). --motion-ease-out itself is declared by the
|
||
decision/quicklook motion block further down; the thread shares it. */
|
||
--motion-ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); /* house signature curve */
|
||
--motion-ease-standard: cubic-bezier(0.4, 0, 0.2, 1); /* house material curve */
|
||
|
||
/* Duration primitives (tunable) */
|
||
--motion-duration-instant: 80ms;
|
||
--motion-duration-fast: 160ms;
|
||
--motion-duration-base: 240ms;
|
||
--motion-duration-slow: 360ms;
|
||
--motion-duration-deliberate: 520ms;
|
||
|
||
/* State/component-scoped — grouped for the tweak panel (tunable) */
|
||
--motion-marker-enter: var(--motion-duration-base);
|
||
--motion-bubble-enter: var(--motion-duration-fast);
|
||
--motion-cot-line-stagger: 40ms;
|
||
--motion-cot-collapse: var(--motion-duration-base);
|
||
--motion-tool-enter: var(--motion-duration-base);
|
||
--motion-diff-reveal: var(--motion-duration-base);
|
||
--motion-status-enter: var(--motion-duration-fast);
|
||
--motion-status-exit: var(--motion-duration-fast);
|
||
--motion-approval-pulse: 1.6s;
|
||
--motion-plan-entry-stagger: 40ms;
|
||
--motion-plan-check: var(--motion-duration-fast);
|
||
--motion-count-tween: var(--motion-duration-slow);
|
||
--motion-streaming-cursor-blink: 1.1s;
|
||
--motion-turn-fold: 380ms; /* finished-turn activity folding into its summary line */
|
||
--motion-line-scroll: 240ms; /* completed interstitial line sliding out of the 1lh live-line viewport */
|
||
--motion-interstitial-dwell: 4000ms; /* interstitial update hold: minimum before the next swaps in, and how long a finished update sits before sliding away */
|
||
--motion-scroll-pill-enter: 200ms; /* scroll-to-latest pill entry */
|
||
--motion-scroll-pill-exit: 400ms; /* scroll-to-latest pill exit */
|
||
--motion-ease-scroll-pill-in: cubic-bezier(0.23, 1, 0.32, 1);
|
||
--motion-ease-scroll-pill-out: cubic-bezier(0.7, 0, 0.84, 0);
|
||
--motion-pane-glide: 320ms; /* properties-pane maximize/restore glide */
|
||
|
||
/* Composer mode-chip hues (v5–v7 decisions): agent + plan reuse the task
|
||
status hues; ask is the v5 blue. Consumed as `--sc` seeds through the
|
||
.status-chip color-mix recipe. */
|
||
--tc-mode-agent: var(--status-task-backlog);
|
||
--tc-mode-plan: var(--status-task-todo);
|
||
--tc-mode-ask: #3b82f6;
|
||
|
||
/* Agent-comment bubble fill (v7 grammar): quiet gray card, no border. */
|
||
--bubble-agent: oklch(0.955 0 0);
|
||
|
||
/* Redesigned thread viewport bound: lets the thread own its scroll (auto-
|
||
follow + pinned composer) inside the page flow. The issue header lives
|
||
INSIDE the scroll viewport (it scrolls away with the messages), so the
|
||
only chrome outside the thread is the top bar plus page padding ≈ 8rem —
|
||
this is what keeps the composer pinned near the viewport bottom. */
|
||
--tc-thread-max-h: calc(100dvh - 8rem);
|
||
|
||
/* Redesigned chat-shell column cap (phase 2c): one token caps the page
|
||
wrapper, thread body, and pinned composer so they widen in lockstep. */
|
||
--tc-shell-max-w: 60rem;
|
||
}
|
||
|
||
.dark {
|
||
--background: oklch(0.145 0 0);
|
||
--foreground: oklch(0.985 0 0);
|
||
--card: oklch(0.205 0 0);
|
||
--card-foreground: oklch(0.985 0 0);
|
||
--popover: oklch(0.205 0 0);
|
||
--popover-foreground: oklch(0.985 0 0);
|
||
--primary: oklch(0.922 0 0);
|
||
--primary-foreground: oklch(0.205 0 0);
|
||
--bubble-agent: oklch(0.24 0 0);
|
||
--secondary: oklch(0.269 0 0);
|
||
--secondary-foreground: oklch(0.985 0 0);
|
||
--muted: oklch(0.269 0 0);
|
||
--muted-foreground: oklch(0.708 0 0);
|
||
--accent: oklch(0.269 0 0);
|
||
--accent-foreground: oklch(0.985 0 0);
|
||
--destructive: oklch(0.637 0.237 25.331); /* Gallery feedback round 1: user preferred master's original dark destructive red over the preset's softer 0.704 0.191 22.216. */
|
||
--destructive-foreground: oklch(0.985 0 0);
|
||
--border: oklch(1 0 0 / 10%);
|
||
--input: oklch(1 0 0 / 15%);
|
||
--ring: oklch(0.556 0 0);
|
||
--chart-1: oklch(0.87 0 0);
|
||
--chart-2: oklch(0.556 0 0);
|
||
--chart-3: oklch(0.439 0 0);
|
||
--chart-4: oklch(0.371 0 0);
|
||
--chart-5: oklch(0.269 0 0);
|
||
--sidebar: oklch(0.205 0 0);
|
||
--sidebar-foreground: oklch(0.985 0 0);
|
||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||
--sidebar-accent: oklch(0.269 0 0);
|
||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||
--sidebar-border: oklch(1 0 0 / 10%);
|
||
--sidebar-ring: oklch(0.556 0 0);
|
||
--chip-match-title-bg: oklch(0.27 0.04 265 / 0.5);
|
||
--chip-match-title-fg: oklch(0.78 0.10 265);
|
||
--chip-match-title-border: oklch(0.4 0.06 265);
|
||
--chip-match-comment-bg: oklch(0.27 0.04 145 / 0.5);
|
||
--chip-match-comment-fg: oklch(0.78 0.08 145);
|
||
--chip-match-comment-border: oklch(0.4 0.05 145);
|
||
--chip-match-document-bg: oklch(0.27 0.04 295 / 0.5);
|
||
--chip-match-document-fg: oklch(0.78 0.08 295);
|
||
--chip-match-document-border: oklch(0.4 0.05 295);
|
||
--chip-match-identifier-bg: var(--muted);
|
||
--chip-match-identifier-fg: var(--muted-foreground);
|
||
--chip-match-identifier-border: var(--border);
|
||
--paperclip-doc-annotation-highlight-open: #a16207;
|
||
--paperclip-doc-annotation-highlight-focused: #ca8a04;
|
||
--paperclip-doc-annotation-highlight-stale: #854d0e;
|
||
--paperclip-doc-annotation-highlight-resolved: #713f12;
|
||
|
||
/* Dark-mode AA overrides for the status-icon hues that need a mode-specific
|
||
value (PAP-238). in_progress / blocked stay identical (same blue / red both
|
||
modes) and in_queue tracks in_progress, so they need no override. */
|
||
--status-task-icon-backlog: #9a958a;
|
||
--status-task-icon-todo: #fbbf24;
|
||
--status-task-icon-in_review: #9474f0; /* deeper than violet-400, reads purple */
|
||
--status-task-icon-done: #34d06f;
|
||
--status-task-icon-cancelled: #9a958a;
|
||
}
|
||
|
||
::highlight(paperclip-doc-annotation-open) {
|
||
background-color: var(--paperclip-doc-annotation-highlight-open);
|
||
color: inherit;
|
||
}
|
||
|
||
::highlight(paperclip-doc-annotation-focused) {
|
||
background-color: var(--paperclip-doc-annotation-highlight-focused);
|
||
color: inherit;
|
||
}
|
||
|
||
::highlight(paperclip-doc-annotation-stale) {
|
||
background-color: var(--paperclip-doc-annotation-highlight-stale);
|
||
color: inherit;
|
||
}
|
||
|
||
::highlight(paperclip-doc-annotation-resolved) {
|
||
background-color: var(--paperclip-doc-annotation-highlight-resolved);
|
||
color: inherit;
|
||
}
|
||
|
||
@layer base {
|
||
* {
|
||
@apply border-border outline-ring/50;
|
||
}
|
||
html {
|
||
height: 100%;
|
||
-webkit-tap-highlight-color: color-mix(in oklab, var(--foreground) 20%, transparent);
|
||
@apply font-sans;
|
||
}
|
||
body {
|
||
@apply antialiased bg-background text-foreground;
|
||
font-family: var(--font-sans);
|
||
height: 100%;
|
||
overflow: hidden;
|
||
}
|
||
h1,
|
||
h2,
|
||
h3 {
|
||
text-wrap: balance;
|
||
}
|
||
/* Prevent double-tap-to-zoom on interactive elements for mobile */
|
||
a,
|
||
button,
|
||
[role="button"],
|
||
input,
|
||
select,
|
||
textarea,
|
||
label {
|
||
touch-action: manipulation;
|
||
}
|
||
/* Let font-mono (utilities layer) override for monospace editors */
|
||
.paperclip-mdxeditor [class*="_placeholder_"],
|
||
.paperclip-mdxeditor-content {
|
||
font-family: inherit;
|
||
}
|
||
}
|
||
|
||
@media (pointer: coarse) {
|
||
button,
|
||
[role="button"],
|
||
input,
|
||
select,
|
||
textarea,
|
||
[data-slot="select-trigger"] {
|
||
min-height: 44px;
|
||
}
|
||
|
||
/* Small inline widgets keep their design size on touch devices —
|
||
forcing 44px here stretches checkboxes, chip menus, and icon buttons
|
||
that live inside dense rows (sidebar headers, issue rows, filter
|
||
popovers). The surrounding row provides the touch area. */
|
||
[data-slot="toggle"],
|
||
[data-slot="checkbox"],
|
||
[data-slot="icon-button"],
|
||
[data-size="xs"],
|
||
[data-size="icon-xs"],
|
||
[data-size="icon-sm"],
|
||
input[type="checkbox"],
|
||
input[type="radio"],
|
||
/* Auto-resizing title fields are vertically centered inside their own bar,
|
||
which already provides the touch area; the 44px floor would top-align the
|
||
single line of text and push it too high (notably on mobile Safari). */
|
||
textarea[data-autosize-title] {
|
||
min-height: 0;
|
||
}
|
||
}
|
||
|
||
/* Dark mode scrollbars */
|
||
.dark {
|
||
color-scheme: dark;
|
||
}
|
||
|
||
.dark *::-webkit-scrollbar {
|
||
width: 8px;
|
||
height: 8px;
|
||
}
|
||
|
||
.dark *::-webkit-scrollbar-track {
|
||
background: oklch(0.205 0 0);
|
||
}
|
||
|
||
.dark *::-webkit-scrollbar-thumb {
|
||
background: oklch(0.4 0 0);
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.dark *::-webkit-scrollbar-thumb:hover {
|
||
background: oklch(0.5 0 0);
|
||
}
|
||
|
||
/* Auto-hide scrollbar: thin, stable gutter with the thumb visible only on hover */
|
||
.scrollbar-auto-hide {
|
||
scrollbar-gutter: stable;
|
||
scrollbar-width: thin;
|
||
scrollbar-color: transparent transparent;
|
||
}
|
||
|
||
.scrollbar-auto-hide::-webkit-scrollbar {
|
||
width: 8px !important;
|
||
height: 8px !important;
|
||
background: transparent !important;
|
||
}
|
||
.scrollbar-auto-hide::-webkit-scrollbar-track {
|
||
background: transparent !important;
|
||
}
|
||
.scrollbar-auto-hide::-webkit-scrollbar-thumb {
|
||
background: transparent !important;
|
||
}
|
||
/* Light mode scrollbar on hover */
|
||
.scrollbar-auto-hide:hover {
|
||
scrollbar-color: oklch(0.7 0 0) transparent;
|
||
}
|
||
.scrollbar-auto-hide:hover::-webkit-scrollbar-track {
|
||
background: transparent !important;
|
||
}
|
||
.scrollbar-auto-hide:hover::-webkit-scrollbar-thumb {
|
||
background: oklch(0.7 0 0) !important;
|
||
border-radius: 999px !important;
|
||
}
|
||
.scrollbar-auto-hide:hover::-webkit-scrollbar-thumb:hover {
|
||
background: oklch(0.6 0 0) !important;
|
||
}
|
||
/* Dark mode scrollbar on hover */
|
||
.dark .scrollbar-auto-hide:hover {
|
||
scrollbar-color: oklch(0.4 0 0) transparent;
|
||
}
|
||
.dark .scrollbar-auto-hide:hover::-webkit-scrollbar-track {
|
||
background: transparent !important;
|
||
}
|
||
.dark .scrollbar-auto-hide:hover::-webkit-scrollbar-thumb {
|
||
background: oklch(0.4 0 0) !important;
|
||
}
|
||
.dark .scrollbar-auto-hide:hover::-webkit-scrollbar-thumb:hover {
|
||
background: oklch(0.5 0 0) !important;
|
||
}
|
||
|
||
/* Typing indicator — three bouncing dots, used inside an agent chat bubble */
|
||
.typing-dots {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 3px;
|
||
padding: 4px 0;
|
||
}
|
||
.typing-dots span {
|
||
width: 5px;
|
||
height: 5px;
|
||
border-radius: 999px;
|
||
background: var(--muted-foreground);
|
||
animation: typing-bounce 1.2s ease-in-out infinite;
|
||
}
|
||
.typing-dots span:nth-child(2) {
|
||
animation-delay: 0.15s;
|
||
}
|
||
.typing-dots span:nth-child(3) {
|
||
animation-delay: 0.3s;
|
||
}
|
||
@keyframes typing-bounce {
|
||
0%,
|
||
60%,
|
||
100% {
|
||
transform: translateY(0);
|
||
opacity: 0.4;
|
||
}
|
||
30% {
|
||
transform: translateY(-3px);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.typing-dots span {
|
||
animation: none;
|
||
opacity: 0.6;
|
||
}
|
||
}
|
||
|
||
/* Task Chat Redesign motion tokens collapse to instant under reduced motion.
|
||
Zeroing the primitives cascades to every scoped token that references them;
|
||
the literal-valued scoped tokens (staggers, loops) are zeroed explicitly. */
|
||
@media (prefers-reduced-motion: reduce) {
|
||
:root {
|
||
--motion-duration-instant: 0ms;
|
||
--motion-duration-fast: 0ms;
|
||
--motion-duration-base: 0ms;
|
||
--motion-duration-slow: 0ms;
|
||
--motion-duration-deliberate: 0ms;
|
||
--motion-cot-line-stagger: 0ms;
|
||
--motion-approval-pulse: 0ms;
|
||
--motion-plan-entry-stagger: 0ms;
|
||
--motion-streaming-cursor-blink: 0ms;
|
||
--motion-turn-fold: 0ms;
|
||
--motion-line-scroll: 0ms;
|
||
/* --motion-interstitial-dwell is deliberately NOT zeroed: it is a pacing
|
||
hold, not a movement — zeroing it would make the interstitial line churn
|
||
faster under reduced motion. Swaps themselves snap (line-scroll: 0). */
|
||
--motion-scroll-pill-enter: 0ms;
|
||
--motion-scroll-pill-exit: 0ms;
|
||
--motion-pane-glide: 0ms;
|
||
}
|
||
}
|
||
|
||
/* ────────────────────────────────────────────────────────────────────────
|
||
Task Chat Redesign animation utilities (flag: enableTaskChatRedesign).
|
||
|
||
These classes are the ONLY place the redesigned thread's motion is applied;
|
||
every duration/easing is a var(--motion-*) reference (never a literal), so
|
||
(a) the no-hardcoded-timing gate passes and (b) the dev tweak panel retunes
|
||
them live by writing the tokens on :root. Keep timing here, in tokens — the
|
||
components only add class names.
|
||
──────────────────────────────────────────────────────────────────────── */
|
||
@keyframes tc-fade-rise {
|
||
from { opacity: 0; transform: translateY(4px); }
|
||
to { opacity: 1; transform: none; }
|
||
}
|
||
@keyframes tc-fade-in {
|
||
from { opacity: 0; }
|
||
to { opacity: 1; }
|
||
}
|
||
@keyframes tc-status-in {
|
||
from { opacity: 0; transform: translateY(2px) scale(0.98); }
|
||
to { opacity: 1; transform: none; }
|
||
}
|
||
@keyframes tc-approval-pulse {
|
||
0%, 100% { box-shadow: 0 0 0 0 hsl(var(--primary) / 0.0); }
|
||
50% { box-shadow: 0 0 0 3px hsl(var(--primary) / 0.18); }
|
||
}
|
||
@keyframes tc-cursor-blink {
|
||
0%, 45% { opacity: 1; }
|
||
55%, 100% { opacity: 0; }
|
||
}
|
||
.tc-enter-bubble { animation: tc-fade-rise var(--motion-bubble-enter) var(--motion-ease-out-expo) both; }
|
||
.tc-enter-marker { animation: tc-fade-in var(--motion-marker-enter) var(--motion-ease-out) both; }
|
||
.tc-enter-tool { animation: tc-fade-rise var(--motion-tool-enter) var(--motion-ease-out-expo) both; }
|
||
.tc-enter-status { animation: tc-status-in var(--motion-status-enter) var(--motion-ease-out-expo) both; }
|
||
.tc-enter-cot-line { animation: tc-fade-rise var(--motion-duration-fast) var(--motion-ease-standard) both; }
|
||
.tc-reveal-diff { animation: tc-fade-in var(--motion-diff-reveal) var(--motion-ease-standard) both; }
|
||
.tc-approval { animation: tc-approval-pulse var(--motion-approval-pulse) var(--motion-ease-standard) infinite; }
|
||
.tc-cursor { animation: tc-cursor-blink var(--motion-streaming-cursor-blink) step-end infinite; }
|
||
.tc-enter-plan-entry { animation: tc-fade-rise var(--motion-plan-check) var(--motion-ease-out-expo) both; }
|
||
/* Live-line viewport (PAP-361): exactly one line-height tall, clipping the
|
||
wrapped interstitial block. On wrap, the inner block translates up whole
|
||
line-heights (set inline as translateY(-(n−1)·1lh)) so the completed line
|
||
slides out the top — transform-only, same motion grammar as tool rows
|
||
animating in. */
|
||
.tc-line-scroll { display: block; height: 1lh; overflow: hidden; }
|
||
.tc-line-scroll-inner { transition: transform var(--motion-line-scroll) var(--motion-ease-standard); will-change: transform; }
|
||
|
||
/* Scroll-to-latest pill (stock shadcn MessageScrollerButton motion, tokenized).
|
||
The pill is centered with translateX(-50%), so the keyframes carry the X
|
||
offset through the whole transform. */
|
||
@keyframes tc-pill-in {
|
||
from { opacity: 0; transform: translate(-50%, 8px) scale(0.9); }
|
||
to { opacity: 1; transform: translate(-50%, 0) scale(1); }
|
||
}
|
||
@keyframes tc-pill-out {
|
||
from { opacity: 1; transform: translate(-50%, 0) scale(1); }
|
||
to { opacity: 0; transform: translate(-50%, 8px) scale(0.9); }
|
||
}
|
||
.tc-scroll-pill-in { animation: tc-pill-in var(--motion-scroll-pill-enter) var(--motion-ease-scroll-pill-in) both; }
|
||
.tc-scroll-pill-out { animation: tc-pill-out var(--motion-scroll-pill-exit) var(--motion-ease-scroll-pill-out) both; }
|
||
|
||
/* Finished-turn fold: grid-rows 1fr→0fr animates height without measuring;
|
||
the single grid child must be overflow-hidden for 0fr to clip. */
|
||
.tc-turn-fold {
|
||
display: grid;
|
||
grid-template-rows: 1fr;
|
||
transition: grid-template-rows var(--motion-turn-fold) var(--motion-ease-standard),
|
||
opacity var(--motion-turn-fold) var(--motion-ease-standard);
|
||
}
|
||
.tc-turn-fold[data-folded="true"] { grid-template-rows: 0fr; opacity: 0; }
|
||
.tc-turn-fold > * { overflow: hidden; min-height: 0; }
|
||
|
||
/* Properties-pane maximize/restore glide (left animates between docked and
|
||
sidebar-flush while the pane is position:fixed). */
|
||
.tc-pane-glide { transition: left var(--motion-pane-glide) var(--motion-ease-standard); }
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.tc-enter-bubble,
|
||
.tc-enter-marker,
|
||
.tc-enter-tool,
|
||
.tc-enter-status,
|
||
.tc-enter-cot-line,
|
||
.tc-reveal-diff,
|
||
.tc-scroll-pill-in,
|
||
.tc-scroll-pill-out,
|
||
.tc-enter-plan-entry { animation: none; }
|
||
.tc-turn-fold,
|
||
.tc-line-scroll-inner,
|
||
.tc-pane-glide { transition: none; }
|
||
/* The pill keyframes carry the X-centering; with animation:none restore it. */
|
||
.tc-scroll-pill-in,
|
||
.tc-scroll-pill-out { transform: translate(-50%, 0); }
|
||
.tc-approval { animation: none; box-shadow: 0 0 0 2px hsl(var(--primary) / 0.18); }
|
||
.tc-cursor { animation: none; opacity: 1; }
|
||
}
|
||
|
||
/* Expandable dialog transition for max-width changes */
|
||
[data-slot="dialog-content"] {
|
||
transition: max-width 200ms cubic-bezier(0.16, 1, 0.3, 1);
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------
|
||
Motion tokens
|
||
---------------------------------------------------------------------------
|
||
The first named motion values in the system. Durations and easings were
|
||
previously written inline at each call site; these give the "snappy, eases
|
||
in and out" behaviour one definition so surfaces stay in step.
|
||
|
||
Exit is deliberately shorter than enter: a thing appearing wants to be
|
||
followed, a thing leaving just needs to get out of the way.
|
||
|
||
The enter curve is the same cubic-bezier the dialog max-width transition
|
||
already used — decelerating hard at the end, which is what reads as "snappy"
|
||
rather than "slow start". Consumers: the decision-card disclosure and the
|
||
task quicklook, both below.
|
||
--------------------------------------------------------------------------- */
|
||
:root {
|
||
--motion-duration-enter: 160ms;
|
||
--motion-duration-exit: 110ms;
|
||
--motion-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
||
--motion-ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||
|
||
/* A crossfading pair must share one duration and one curve. Two elements
|
||
swapping places both change height, and the container shows the SUM. Give
|
||
them different timing — the outgoing on a 110ms ease-in against the
|
||
incoming on a 160ms ease-out — and that sum stops being monotonic: the
|
||
container bulges or dips mid-swap, which reads as a lurch even though each
|
||
half is individually smooth. Run both off the same clock and the same
|
||
curve and the sum collapses to a clean interpolation between the two
|
||
states, because at every moment progress is identical on both sides. */
|
||
--motion-duration-swap: 150ms;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------
|
||
Decision-card disclosure (See more / See less)
|
||
---------------------------------------------------------------------------
|
||
Radix Collapsible measures the panel and publishes its height, so the card
|
||
can grow and shrink to a real number instead of snapping. The content
|
||
unmounts once the exit finishes, so an expanded row's resolver is not left
|
||
running behind a collapsed card.
|
||
--------------------------------------------------------------------------- */
|
||
@keyframes decision-disclosure-open {
|
||
from {
|
||
height: 0;
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
height: var(--radix-collapsible-content-height);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
@keyframes decision-disclosure-close {
|
||
from {
|
||
height: var(--radix-collapsible-content-height);
|
||
opacity: 1;
|
||
}
|
||
to {
|
||
height: 0;
|
||
opacity: 0;
|
||
}
|
||
}
|
||
[data-slot="collapsible-content"][data-decision-disclosure] {
|
||
overflow: hidden;
|
||
}
|
||
[data-slot="collapsible-content"][data-decision-disclosure][data-state="open"] {
|
||
animation: decision-disclosure-open var(--motion-duration-swap) var(--motion-ease-out);
|
||
}
|
||
[data-slot="collapsible-content"][data-decision-disclosure][data-state="closed"] {
|
||
/* Same duration and same curve as the open side — see --motion-duration-swap.
|
||
The two run against each other, so matched timing is what keeps the card's
|
||
height a straight interpolation instead of a bulge. */
|
||
animation: decision-disclosure-close var(--motion-duration-swap) var(--motion-ease-out);
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------
|
||
Task quicklook: a small scale-up out of the trigger, not a fade in place.
|
||
---------------------------------------------------------------------------
|
||
The shadcn PopoverContent class string carries `animate-in`, `zoom-in-95`
|
||
and friends, but those utilities come from the tailwindcss-animate plugin,
|
||
which this project does not install — there is no `enter`/`exit` keyframe in
|
||
the build, so they are inert. Rather than take on that dependency for one
|
||
surface (it would newly animate every dialog, dropdown and tooltip in the
|
||
app), the quicklook defines its own motion here.
|
||
|
||
The scale is deliberately shallow. `--radix-popover-content-transform-origin`
|
||
pins the origin to the corner nearest the trigger, so the card reads as
|
||
growing out of the task key that opened it rather than swelling in place.
|
||
--------------------------------------------------------------------------- */
|
||
@keyframes quicklook-open {
|
||
from {
|
||
opacity: 0;
|
||
transform: scale(0.96);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: scale(1);
|
||
}
|
||
}
|
||
@keyframes quicklook-close {
|
||
from {
|
||
opacity: 1;
|
||
transform: scale(1);
|
||
}
|
||
to {
|
||
opacity: 0;
|
||
transform: scale(0.96);
|
||
}
|
||
}
|
||
[data-slot="popover-content"][data-quicklook][data-state="open"] {
|
||
animation: quicklook-open var(--motion-duration-enter) var(--motion-ease-out);
|
||
}
|
||
[data-slot="popover-content"][data-quicklook][data-state="closed"] {
|
||
animation: quicklook-close var(--motion-duration-exit) var(--motion-ease-in);
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
[data-slot="collapsible-content"][data-decision-disclosure][data-state="open"],
|
||
[data-slot="collapsible-content"][data-decision-disclosure][data-state="closed"],
|
||
[data-slot="popover-content"][data-quicklook][data-state="open"],
|
||
[data-slot="popover-content"][data-quicklook][data-state="closed"] {
|
||
animation: none;
|
||
}
|
||
}
|
||
|
||
/* Dashboard activity row entry motion */
|
||
@keyframes dashboard-activity-enter {
|
||
0% {
|
||
opacity: 0;
|
||
transform: translateY(-14px) scale(0.985);
|
||
filter: blur(4px);
|
||
}
|
||
62% {
|
||
opacity: 1;
|
||
transform: translateY(2px) scale(1.002);
|
||
filter: blur(0);
|
||
}
|
||
100% {
|
||
opacity: 1;
|
||
transform: translateY(0) scale(1);
|
||
filter: blur(0);
|
||
}
|
||
}
|
||
|
||
@keyframes dashboard-activity-highlight {
|
||
0% {
|
||
box-shadow: inset 2px 0 0 var(--primary);
|
||
background-color: color-mix(in oklab, var(--accent) 55%, transparent);
|
||
}
|
||
100% {
|
||
box-shadow: inset 0 0 0 transparent;
|
||
background-color: transparent;
|
||
}
|
||
}
|
||
|
||
.activity-row-enter {
|
||
animation:
|
||
dashboard-activity-enter 520ms cubic-bezier(0.16, 1, 0.3, 1),
|
||
dashboard-activity-highlight 920ms cubic-bezier(0.16, 1, 0.3, 1);
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.activity-row-enter {
|
||
animation: none;
|
||
}
|
||
}
|
||
|
||
/* Chain-of-thought reasoning line ticker animations.
|
||
Pure translate, no opacity — the overflow-hidden container clips.
|
||
Both keyframes share the same easing so the two spans move in lockstep. */
|
||
@keyframes cot-line-slide-in {
|
||
from { transform: translateY(100%); }
|
||
to { transform: translateY(0); }
|
||
}
|
||
|
||
@keyframes cot-line-slide-out {
|
||
from { transform: translateY(0); }
|
||
to { transform: translateY(-100%); }
|
||
}
|
||
|
||
.cot-line-enter {
|
||
animation: cot-line-slide-in 300ms cubic-bezier(0.4, 0, 0.2, 1) both;
|
||
}
|
||
|
||
.cot-line-exit {
|
||
animation: cot-line-slide-out 300ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.cot-line-enter,
|
||
.cot-line-exit {
|
||
animation: none;
|
||
}
|
||
}
|
||
|
||
/* Shimmer text effect for active "Working" state — Cursor-style sweep */
|
||
@keyframes shimmer-text-slide {
|
||
0% { background-position: 100% center; }
|
||
60% { background-position: 0% center; }
|
||
100% { background-position: 0% center; }
|
||
}
|
||
|
||
.shimmer-text {
|
||
--shimmer-base: var(--foreground);
|
||
--shimmer-highlight: color-mix(in oklch, var(--foreground) 35%, transparent);
|
||
background: linear-gradient(
|
||
90deg,
|
||
var(--shimmer-base) 0%,
|
||
var(--shimmer-base) 40%,
|
||
var(--shimmer-highlight) 50%,
|
||
var(--shimmer-base) 60%,
|
||
var(--shimmer-base) 100%
|
||
);
|
||
background-size: 200% 100%;
|
||
-webkit-background-clip: text;
|
||
background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
animation: shimmer-text-slide 2.5s linear infinite;
|
||
}
|
||
|
||
.paperclip-thinking-icon {
|
||
transform-origin: 50% 50%;
|
||
}
|
||
|
||
@keyframes paperclip-thinking-draw {
|
||
0% {
|
||
stroke-dasharray: 0 85.717;
|
||
stroke-dashoffset: -85.717;
|
||
opacity: 1;
|
||
animation-timing-function: cubic-bezier(0.455, 0.03, 0.515, 0.955);
|
||
}
|
||
39.0625% {
|
||
stroke-dasharray: 85.717 85.717;
|
||
stroke-dashoffset: 0;
|
||
opacity: 1;
|
||
animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
|
||
}
|
||
78.125% {
|
||
stroke-dasharray: 0 85.717;
|
||
stroke-dashoffset: 0;
|
||
opacity: 1;
|
||
}
|
||
78.225% {
|
||
opacity: 0;
|
||
}
|
||
100% {
|
||
stroke-dasharray: 0 85.717;
|
||
stroke-dashoffset: 0;
|
||
opacity: 0;
|
||
}
|
||
}
|
||
|
||
.paperclip-thinking-icon-path {
|
||
animation: paperclip-thinking-draw 1s linear infinite;
|
||
}
|
||
|
||
/* Muted variant (task-chat v7 activity rows): the sweep runs over the
|
||
muted-foreground tier so live "Thinking…"/"Working…" labels stay subordinate
|
||
to real content. */
|
||
.shimmer-text-muted {
|
||
--shimmer-base: var(--muted-foreground);
|
||
--shimmer-highlight: color-mix(in oklch, var(--muted-foreground) 30%, transparent);
|
||
}
|
||
|
||
/* Utilities expected by shadcn base components (ui/attachment.tsx). Declared
|
||
with @utility so variant forms (e.g. group-data-[state=uploading]:shimmer)
|
||
compile under Tailwind v4. */
|
||
@utility shimmer {
|
||
--shimmer-base: var(--foreground);
|
||
--shimmer-highlight: color-mix(in oklch, var(--foreground) 35%, transparent);
|
||
background: linear-gradient(
|
||
90deg,
|
||
var(--shimmer-base) 0%,
|
||
var(--shimmer-base) 40%,
|
||
var(--shimmer-highlight) 50%,
|
||
var(--shimmer-base) 60%,
|
||
var(--shimmer-base) 100%
|
||
);
|
||
background-size: 200% 100%;
|
||
-webkit-background-clip: text;
|
||
background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
animation: shimmer-text-slide 2.5s linear infinite;
|
||
}
|
||
|
||
@utility scrollbar-none {
|
||
scrollbar-width: none;
|
||
&::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
@utility scroll-fade-x {
|
||
mask-image: linear-gradient(
|
||
to right,
|
||
transparent,
|
||
black 12px,
|
||
black calc(100% - 12px),
|
||
transparent
|
||
);
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.shimmer-text {
|
||
animation: none;
|
||
-webkit-text-fill-color: unset;
|
||
background: none;
|
||
}
|
||
|
||
.paperclip-thinking-icon-path {
|
||
animation: none;
|
||
opacity: 1;
|
||
stroke-dasharray: 85.717 85.717;
|
||
stroke-dashoffset: 0;
|
||
}
|
||
}
|
||
|
||
/* Agent heartbeat capsule motion (PAP-75).
|
||
`hb-pulse` marks a running agent (live); `hb-blink` marks a broken agent
|
||
(error). The blink alternates the fill between brand red and the page
|
||
background so the capsule reads as a faltering heartbeat. */
|
||
@keyframes hb-pulse {
|
||
0%, 100% { opacity: 0.4; transform: scale(0.85); }
|
||
50% { opacity: 1; transform: scale(1); }
|
||
}
|
||
|
||
@keyframes hb-blink {
|
||
0%, 49.9% { background-color: #DC2626; }
|
||
50%, 100% { background-color: var(--background); }
|
||
}
|
||
|
||
.hb-pulse {
|
||
animation: hb-pulse 1.6s ease-in-out infinite;
|
||
}
|
||
|
||
.hb-blink {
|
||
animation: hb-blink 1.2s step-end infinite;
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.hb-pulse,
|
||
.hb-blink {
|
||
animation: none;
|
||
}
|
||
}
|
||
|
||
/* Agent capsule motif (PAP-119) — "the capsule is the agent".
|
||
Three states realized in the AgentCapsule component:
|
||
- slot: static dashed outline (an empty agent slot)
|
||
- configured: solid stroke (named/model picked, not yet live)
|
||
- online: brand agent-gradient fill radiating outward from the center
|
||
+ online-pulse (the only pulsing state)
|
||
prefers-reduced-motion skips the fill + pulses and renders the final
|
||
state. */
|
||
/* Radial outward reveal of the (linear-gradient) fill: a centered circular
|
||
clip grows past the half-diagonal (~71%) so it covers the whole capsule. */
|
||
@keyframes agent-cap-fill-radial {
|
||
0% { clip-path: circle(0% at 50% 50%); }
|
||
100% { clip-path: circle(75% at 50% 50%); }
|
||
}
|
||
|
||
@keyframes agent-cap-online-pulse {
|
||
0%, 100% {
|
||
box-shadow:
|
||
0 0 0 0 color-mix(in oklab, #22c55e 0%, transparent),
|
||
0 0 0 0 color-mix(in oklab, #22c55e 0%, transparent);
|
||
transform: scale(1);
|
||
}
|
||
50% {
|
||
box-shadow:
|
||
0 0 0 6px color-mix(in oklab, #22c55e 18%, transparent),
|
||
0 0 0 14px color-mix(in oklab, #22c55e 7%, transparent);
|
||
transform: scale(1.03);
|
||
}
|
||
}
|
||
|
||
.agent-cap-liquid {
|
||
/* Dramatic ease-in-out: the radial fill eases in, rushes, and eases out. */
|
||
animation: agent-cap-fill-radial var(--agent-cap-reveal-duration, 1.4s)
|
||
cubic-bezier(0.83, 0, 0.17, 1) forwards;
|
||
}
|
||
|
||
/* Online carryover: the solid white outline begins fading as soon as the fill
|
||
starts so the two overlap toward the start (outline gone by the fill's
|
||
midpoint). Shares the reveal duration so an onboarding-scoped override slows
|
||
both together. */
|
||
@keyframes agent-cap-stroke-carryover {
|
||
0% {
|
||
opacity: 1;
|
||
}
|
||
50%,
|
||
100% {
|
||
opacity: 0;
|
||
}
|
||
}
|
||
|
||
.agent-cap-stroke-carryover {
|
||
animation: agent-cap-stroke-carryover var(--agent-cap-reveal-duration, 1.4s)
|
||
linear forwards;
|
||
}
|
||
|
||
.agent-cap-online {
|
||
animation: agent-cap-online-pulse 1.8s ease-in-out infinite;
|
||
}
|
||
|
||
/* Blue online-glow variant (PAP-125) — the Option 4 wizard's review step.
|
||
Same breathing ring as the green pulse, recoloured to brand blue (#2563eb). */
|
||
@keyframes agent-cap-online-pulse-blue {
|
||
0%, 100% {
|
||
box-shadow:
|
||
0 0 0 0 color-mix(in oklab, #2563eb 0%, transparent),
|
||
0 0 0 0 color-mix(in oklab, #2563eb 0%, transparent);
|
||
transform: scale(1);
|
||
}
|
||
50% {
|
||
box-shadow:
|
||
0 0 0 6px color-mix(in oklab, #2563eb 22%, transparent),
|
||
0 0 0 14px color-mix(in oklab, #2563eb 9%, transparent);
|
||
transform: scale(1.03);
|
||
}
|
||
}
|
||
|
||
.agent-cap-online-blue {
|
||
animation: agent-cap-online-pulse-blue 1.8s ease-in-out infinite;
|
||
}
|
||
|
||
/* Cross-fade the stacked dashed/solid border layers as the capsule evolves
|
||
in place (border-style itself is not animatable). */
|
||
.agent-cap-layer {
|
||
transition: opacity 0.5s ease;
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.agent-cap-online,
|
||
.agent-cap-online-blue {
|
||
animation: none;
|
||
}
|
||
.agent-cap-layer {
|
||
transition: none;
|
||
}
|
||
.agent-cap-liquid {
|
||
animation: none;
|
||
clip-path: none;
|
||
}
|
||
.agent-cap-stroke-carryover {
|
||
animation: none;
|
||
opacity: 0;
|
||
}
|
||
}
|
||
|
||
/* MDXEditor theme integration */
|
||
.paperclip-mdxeditor-scope,
|
||
.paperclip-mdxeditor {
|
||
--baseBase: var(--background);
|
||
--baseBg: transparent;
|
||
--baseBgSubtle: color-mix(in oklab, var(--accent) 35%, transparent);
|
||
--baseLine: var(--border);
|
||
--baseSolid: var(--muted-foreground);
|
||
--baseSolidHover: var(--foreground);
|
||
--baseText: var(--muted-foreground);
|
||
--baseBorderColor: var(--border);
|
||
--baseBorder: var(--border);
|
||
--baseBorderHover: var(--ring);
|
||
--baseTextContrast: var(--foreground);
|
||
--baseTextContrastMuted: var(--muted-foreground);
|
||
--baseTextEmphasis: var(--foreground);
|
||
--basePageBg: var(--background);
|
||
--baseRadius: var(--radius);
|
||
--baseLineHeight: 1.5;
|
||
--accentBorder: color-mix(in oklab, var(--primary) 35%, var(--border));
|
||
--accentSolid: var(--primary);
|
||
--accentSolidHover: var(--primary);
|
||
--accentLine: color-mix(in oklab, var(--primary) 20%, transparent);
|
||
--accentBg: var(--accent);
|
||
--accentBgHover: color-mix(in oklab, var(--accent) 80%, var(--background));
|
||
--accentBgActive: color-mix(in oklab, var(--accent) 72%, var(--background));
|
||
--accentText: var(--accent-foreground);
|
||
font-family: inherit;
|
||
font-size: 1rem;
|
||
line-height: 1.5;
|
||
color: var(--foreground);
|
||
}
|
||
|
||
@media (min-width: 768px) {
|
||
.paperclip-mdxeditor-scope,
|
||
.paperclip-mdxeditor {
|
||
font-size: 0.875rem;
|
||
}
|
||
}
|
||
|
||
.paperclip-mdxeditor-scope [class*="_iconButton_"],
|
||
.paperclip-mdxeditor [class*="_iconButton_"] {
|
||
color: var(--baseText);
|
||
}
|
||
|
||
.paperclip-mdxeditor-scope [class*="_iconButton_"]:hover,
|
||
.paperclip-mdxeditor [class*="_iconButton_"]:hover {
|
||
color: var(--baseTextContrast);
|
||
}
|
||
|
||
.paperclip-mdxeditor .mdxeditor-root-contenteditable {
|
||
min-height: 2.5rem;
|
||
padding: 0;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.paperclip-mdxeditor [class*="_contentEditable_"] {
|
||
padding: 0.375rem 0.625rem !important;
|
||
}
|
||
|
||
.paperclip-mdxeditor--borderless [class*="_contentEditable_"] {
|
||
padding: 0 !important;
|
||
}
|
||
|
||
.paperclip-mdxeditor [class*="_placeholder_"] {
|
||
font-size: inherit;
|
||
line-height: 1.5;
|
||
color: var(--muted-foreground);
|
||
}
|
||
|
||
.paperclip-mdxeditor-content {
|
||
font-size: inherit;
|
||
line-height: inherit;
|
||
color: inherit;
|
||
}
|
||
|
||
.paperclip-edit-in-place-content {
|
||
font-size: 0.9375rem;
|
||
line-height: 1.75rem;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content > *:first-child {
|
||
margin-top: 0;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content > *:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content p {
|
||
margin: 0;
|
||
line-height: inherit;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content p + p {
|
||
margin-top: 1.1em;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content a:not(.paperclip-mention-chip):not(.paperclip-project-mention-chip) {
|
||
color: color-mix(in oklab, var(--foreground) 76%, #0969da 24%);
|
||
text-decoration: underline;
|
||
text-underline-offset: 0.15em;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.dark .paperclip-mdxeditor-content a:not(.paperclip-mention-chip):not(.paperclip-project-mention-chip) {
|
||
color: color-mix(in oklab, var(--foreground) 80%, #58a6ff 20%);
|
||
}
|
||
|
||
.paperclip-mdxeditor-content a.paperclip-mention-chip,
|
||
.paperclip-mdxeditor-content a.paperclip-project-mention-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.25rem;
|
||
margin: 0 0.1rem;
|
||
padding: 0 0.625rem;
|
||
border: 1px solid var(--border);
|
||
border-radius: 999px;
|
||
font-size: 0.75rem;
|
||
line-height: 1.25;
|
||
text-decoration: none;
|
||
vertical-align: middle;
|
||
position: relative;
|
||
top: -1px;
|
||
white-space: nowrap;
|
||
user-select: none;
|
||
}
|
||
|
||
/* Strip the MDXEditor's default inline-code styling from the text inside chips
|
||
(the link label otherwise picks up a monospace font + gray tint). */
|
||
.paperclip-mdxeditor-content a.paperclip-mention-chip code,
|
||
.paperclip-mdxeditor-content a.paperclip-project-mention-chip code {
|
||
font-family: inherit;
|
||
background: none;
|
||
color: inherit;
|
||
padding: 0;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content a.paperclip-mention-chip::before,
|
||
a.paperclip-mention-chip::before {
|
||
content: "";
|
||
flex: none;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content a.paperclip-mention-chip[data-mention-kind="project"]::before,
|
||
a.paperclip-mention-chip[data-mention-kind="project"]::before {
|
||
width: 0.45rem;
|
||
height: 0.45rem;
|
||
border-radius: 999px;
|
||
background-color: var(--paperclip-mention-project-color, currentColor);
|
||
}
|
||
|
||
.paperclip-mdxeditor-content a.paperclip-mention-chip[data-mention-kind="agent"]::before,
|
||
a.paperclip-mention-chip[data-mention-kind="agent"]::before {
|
||
width: 0.75rem;
|
||
height: 0.75rem;
|
||
background-color: currentColor;
|
||
-webkit-mask-image: var(--paperclip-mention-icon-mask);
|
||
mask-image: var(--paperclip-mention-icon-mask);
|
||
-webkit-mask-position: center;
|
||
mask-position: center;
|
||
-webkit-mask-repeat: no-repeat;
|
||
mask-repeat: no-repeat;
|
||
-webkit-mask-size: contain;
|
||
mask-size: contain;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content ul,
|
||
.paperclip-mdxeditor-content ol {
|
||
margin: 1.1em 0;
|
||
padding-left: 1.6em;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content ul {
|
||
list-style: disc;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content ol {
|
||
list-style: decimal;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content li {
|
||
display: list-item;
|
||
margin: 0.3em 0;
|
||
line-height: inherit;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content li::marker {
|
||
color: var(--muted-foreground);
|
||
}
|
||
|
||
.paperclip-mdxeditor-content h1 {
|
||
margin: 1.4em 0 0.9em;
|
||
font-size: 1.75em;
|
||
font-weight: 700;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content h2 {
|
||
margin: 1.3em 0 0.85em;
|
||
font-size: 1.35em;
|
||
font-weight: 700;
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content h3 {
|
||
margin: 1.2em 0 0.8em;
|
||
font-size: 1.15em;
|
||
font-weight: 600;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content img {
|
||
max-height: 18rem;
|
||
border-radius: calc(var(--radius) - 2px);
|
||
}
|
||
|
||
.paperclip-mdxeditor-content blockquote {
|
||
margin: 1.2em 0;
|
||
padding-left: 1em;
|
||
border-left: 3px solid var(--border);
|
||
color: var(--muted-foreground);
|
||
line-height: inherit;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content code {
|
||
font-family: var(--font-mono);
|
||
font-size: 1em;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content pre {
|
||
margin: 0.4rem 0;
|
||
padding: 0;
|
||
border: 1px solid color-mix(in oklab, var(--foreground) 12%, transparent);
|
||
border-radius: calc(var(--radius) - 3px);
|
||
background: #1e1e2e;
|
||
color: #cdd6f4;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
/* Dark theme for CodeMirror code blocks inside the MDXEditor.
|
||
Overrides the default cm6-theme-basic-light that MDXEditor bundles. */
|
||
.paperclip-mdxeditor .cm-editor {
|
||
background-color: #1e1e2e !important;
|
||
color: #cdd6f4 !important;
|
||
font-size: 1em;
|
||
}
|
||
|
||
.paperclip-mdxeditor .cm-gutters {
|
||
background-color: #181825 !important;
|
||
color: #585b70 !important;
|
||
border-right: 1px solid #313244 !important;
|
||
}
|
||
|
||
.paperclip-mdxeditor .cm-activeLineGutter {
|
||
background-color: #1e1e2e !important;
|
||
}
|
||
|
||
.paperclip-mdxeditor .cm-activeLine {
|
||
background-color: color-mix(in oklab, #cdd6f4 5%, transparent) !important;
|
||
}
|
||
|
||
.paperclip-mdxeditor .cm-cursor,
|
||
.paperclip-mdxeditor .cm-dropCursor {
|
||
border-left-color: #cdd6f4 !important;
|
||
}
|
||
|
||
.paperclip-mdxeditor .cm-selectionBackground {
|
||
background-color: color-mix(in oklab, #89b4fa 25%, transparent) !important;
|
||
}
|
||
|
||
.paperclip-mdxeditor .cm-focused .cm-selectionBackground {
|
||
background-color: color-mix(in oklab, #89b4fa 30%, transparent) !important;
|
||
}
|
||
|
||
.paperclip-mdxeditor .cm-content {
|
||
caret-color: #cdd6f4;
|
||
}
|
||
|
||
/* MDXEditor code block language selector – show on hover only */
|
||
.paperclip-mdxeditor-content [class*="_codeMirrorWrapper_"] {
|
||
position: relative;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content [class*="_codeMirrorToolbar_"],
|
||
.paperclip-mdxeditor-content [class*="_codeBlockToolbar_"] {
|
||
position: absolute;
|
||
top: 0.25rem;
|
||
right: 0.25rem;
|
||
z-index: 2;
|
||
opacity: 0;
|
||
transition: opacity 150ms ease;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content [class*="_codeMirrorToolbar_"] select,
|
||
.paperclip-mdxeditor-content [class*="_codeBlockToolbar_"] select {
|
||
background-color: #313244;
|
||
color: #cdd6f4;
|
||
border-color: #45475a;
|
||
}
|
||
|
||
.paperclip-mdxeditor-content [class*="_codeMirrorWrapper_"]:hover [class*="_codeMirrorToolbar_"],
|
||
.paperclip-mdxeditor-content [class*="_codeMirrorWrapper_"]:hover [class*="_codeBlockToolbar_"],
|
||
.paperclip-mdxeditor-content [class*="_codeMirrorWrapper_"]:focus-within [class*="_codeMirrorToolbar_"],
|
||
.paperclip-mdxeditor-content [class*="_codeMirrorWrapper_"]:focus-within [class*="_codeBlockToolbar_"] {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* Rendered markdown code blocks & inline code (prose/MarkdownBody context).
|
||
Dark theme code blocks with compact sizing.
|
||
Override prose CSS variables so prose-invert can't revert to defaults. */
|
||
.paperclip-markdown {
|
||
--tw-prose-pre-bg: #1e1e2e;
|
||
--tw-prose-pre-code: #cdd6f4;
|
||
--tw-prose-invert-pre-bg: #1e1e2e;
|
||
--tw-prose-invert-pre-code: #cdd6f4;
|
||
}
|
||
|
||
.paperclip-markdown pre {
|
||
border: 1px solid color-mix(in oklab, var(--foreground) 12%, transparent) !important;
|
||
border-radius: calc(var(--radius) - 3px) !important;
|
||
background-color: #1e1e2e !important;
|
||
color: #cdd6f4 !important;
|
||
padding: 0.5rem 0.65rem !important;
|
||
margin: 0.4rem 0 !important;
|
||
font-size: 1em !important;
|
||
overflow-x: auto;
|
||
white-space: pre;
|
||
}
|
||
|
||
.paperclip-markdown code {
|
||
font-family: var(--font-mono);
|
||
font-size: 1em;
|
||
}
|
||
|
||
.paperclip-markdown pre code {
|
||
font-size: inherit;
|
||
color: inherit;
|
||
background: none;
|
||
}
|
||
|
||
/* Actions for fenced and indented preformatted markdown blocks */
|
||
.paperclip-markdown-codeblock {
|
||
position: relative;
|
||
}
|
||
|
||
.paperclip-markdown-codeblock-actions {
|
||
position: absolute;
|
||
top: 0.4rem;
|
||
right: 0.4rem;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.25rem;
|
||
opacity: 0;
|
||
transition: opacity 0.12s ease;
|
||
}
|
||
|
||
.paperclip-markdown-codeblock-action {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 0.25rem;
|
||
min-height: 1.55rem;
|
||
padding: 0.2rem 0.4rem;
|
||
border-radius: calc(var(--radius) - 4px);
|
||
border: 1px solid color-mix(in oklab, var(--foreground) 14%, transparent);
|
||
background-color: color-mix(in oklab, var(--muted) 92%, var(--background) 8%);
|
||
color: var(--muted-foreground);
|
||
font-size: 0.7rem;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
transition: background-color 0.12s ease, color 0.12s ease, border-color 0.12s ease;
|
||
}
|
||
|
||
.paperclip-markdown-codeblock:hover .paperclip-markdown-codeblock-actions,
|
||
.paperclip-markdown-codeblock-actions:focus-within,
|
||
.paperclip-markdown-codeblock-actions[data-active] {
|
||
opacity: 1;
|
||
}
|
||
|
||
.paperclip-markdown-codeblock-action:hover {
|
||
background-color: var(--accent);
|
||
color: var(--accent-foreground);
|
||
}
|
||
|
||
.paperclip-markdown-codeblock-action[data-active],
|
||
.paperclip-markdown-codeblock-action[data-copied] {
|
||
border-color: color-mix(in oklab, var(--primary) 38%, transparent);
|
||
color: var(--primary);
|
||
}
|
||
|
||
.paperclip-markdown-codeblock-action[data-failed] {
|
||
color: var(--destructive);
|
||
}
|
||
|
||
.paperclip-markdown-codeblock-action-label {
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* Remove backtick pseudo-elements from inline code (prose default adds them) */
|
||
.prose code::before,
|
||
.prose code::after {
|
||
content: none;
|
||
}
|
||
|
||
/* Inline code background (not inside a code block) */
|
||
.prose :not(pre) > code {
|
||
background-color: color-mix(in oklab, var(--accent) 60%, transparent);
|
||
padding: 0.15em 0.35em;
|
||
border-radius: 3px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.dark .prose :not(pre) > code {
|
||
background-color: #ffffff0f;
|
||
}
|
||
|
||
.paperclip-markdown.paperclip-markdown-on-accent :not(pre) > code {
|
||
background-color: color-mix(in oklab, currentColor 24%, transparent);
|
||
color: currentColor;
|
||
}
|
||
|
||
.paperclip-markdown {
|
||
color: var(--foreground);
|
||
font-size: 0.9375rem;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.paperclip-markdown.paperclip-markdown-on-accent {
|
||
color: inherit;
|
||
--tw-prose-body: currentColor;
|
||
--tw-prose-headings: currentColor;
|
||
--tw-prose-lead: currentColor;
|
||
--tw-prose-links: currentColor;
|
||
--tw-prose-bold: currentColor;
|
||
--tw-prose-counters: currentColor;
|
||
--tw-prose-bullets: currentColor;
|
||
--tw-prose-hr: color-mix(in oklab, currentColor 24%, transparent);
|
||
--tw-prose-quotes: currentColor;
|
||
--tw-prose-quote-borders: color-mix(in oklab, currentColor 56%, transparent);
|
||
--tw-prose-captions: color-mix(in oklab, currentColor 84%, transparent);
|
||
--tw-prose-code: currentColor;
|
||
--tw-prose-th-borders: color-mix(in oklab, currentColor 34%, transparent);
|
||
--tw-prose-td-borders: color-mix(in oklab, currentColor 22%, transparent);
|
||
--tw-prose-invert-body: currentColor;
|
||
--tw-prose-invert-headings: currentColor;
|
||
--tw-prose-invert-lead: currentColor;
|
||
--tw-prose-invert-links: currentColor;
|
||
--tw-prose-invert-bold: currentColor;
|
||
--tw-prose-invert-counters: currentColor;
|
||
--tw-prose-invert-bullets: currentColor;
|
||
--tw-prose-invert-hr: color-mix(in oklab, currentColor 24%, transparent);
|
||
--tw-prose-invert-quotes: currentColor;
|
||
--tw-prose-invert-quote-borders: color-mix(in oklab, currentColor 56%, transparent);
|
||
--tw-prose-invert-captions: color-mix(in oklab, currentColor 84%, transparent);
|
||
--tw-prose-invert-code: currentColor;
|
||
--tw-prose-invert-th-borders: color-mix(in oklab, currentColor 34%, transparent);
|
||
--tw-prose-invert-td-borders: color-mix(in oklab, currentColor 22%, transparent);
|
||
}
|
||
|
||
.paperclip-markdown > :first-child {
|
||
margin-top: 0;
|
||
}
|
||
|
||
.paperclip-markdown > :last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.paperclip-markdown :where(p, ul, ol, blockquote, pre, .paperclip-markdown-table-scroll) {
|
||
margin-top: 0.7rem;
|
||
margin-bottom: 0.7rem;
|
||
}
|
||
|
||
.paperclip-markdown :where(ul, ol) {
|
||
padding-left: 1.5rem;
|
||
}
|
||
|
||
.paperclip-markdown ul {
|
||
list-style-type: disc;
|
||
}
|
||
|
||
.paperclip-markdown ol {
|
||
/* Enough inline start room to hold outside markers without clipped digits
|
||
when markdown lists sit flush against an overflow-constrained container. */
|
||
list-style-type: decimal;
|
||
padding-left: 2.5rem;
|
||
}
|
||
|
||
.paperclip-markdown li {
|
||
margin: 0.14rem 0;
|
||
padding-left: 0.2rem;
|
||
}
|
||
|
||
.paperclip-markdown li > :where(p, ul, ol) {
|
||
margin-top: 0.3rem;
|
||
margin-bottom: 0.3rem;
|
||
}
|
||
|
||
.paperclip-markdown li::marker {
|
||
color: var(--muted-foreground);
|
||
}
|
||
|
||
.paperclip-markdown.paperclip-markdown-on-accent li::marker {
|
||
color: currentColor;
|
||
}
|
||
|
||
.paperclip-markdown h1,
|
||
.paperclip-markdown h2,
|
||
.paperclip-markdown h3,
|
||
.paperclip-markdown h4 {
|
||
margin-top: 1.75rem;
|
||
margin-bottom: 0.45rem;
|
||
color: var(--foreground);
|
||
font-weight: 600;
|
||
letter-spacing: -0.01em;
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.paperclip-markdown h1 {
|
||
font-size: 1.5rem;
|
||
}
|
||
|
||
.paperclip-markdown h2 {
|
||
font-size: 1.25rem;
|
||
}
|
||
|
||
.paperclip-markdown h3 {
|
||
font-size: 1.05rem;
|
||
}
|
||
|
||
.paperclip-markdown h4 {
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.paperclip-markdown.paperclip-markdown-on-accent :where(h1, h2, h3, h4, strong, b) {
|
||
color: currentColor;
|
||
}
|
||
|
||
.paperclip-markdown :where(strong, b) {
|
||
color: var(--foreground);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.paperclip-markdown a {
|
||
color: color-mix(in oklab, var(--foreground) 76%, #0969da 24%);
|
||
text-decoration: underline;
|
||
text-underline-offset: 0.15em;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.paperclip-markdown.paperclip-markdown-on-accent :where(a, a:visited) {
|
||
color: currentColor;
|
||
text-decoration-color: color-mix(in oklab, currentColor 72%, transparent);
|
||
}
|
||
|
||
.paperclip-markdown.paperclip-markdown-on-accent :where(a:hover, a:focus-visible) {
|
||
color: currentColor;
|
||
text-decoration-color: currentColor;
|
||
}
|
||
|
||
.paperclip-markdown a.paperclip-mention-chip {
|
||
text-decoration: none;
|
||
}
|
||
|
||
.paperclip-markdown a.paperclip-mention-chip[data-mention-kind="issue"] {
|
||
border-color: color-mix(in oklab, var(--foreground) 14%, var(--border) 86%);
|
||
background: color-mix(in oklab, var(--accent) 42%, transparent);
|
||
}
|
||
|
||
/* Inline issue references in markdown: no pill chrome, just a status icon
|
||
beside the link label — keeps the pair from splitting across lines. */
|
||
.paperclip-markdown-issue-ref {
|
||
display: inline;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* External object URL decorations follow the same pattern: the host renders
|
||
the icon prefix, the link text is left untouched (Postel's Law). */
|
||
.paperclip-markdown-external-ref {
|
||
display: inline;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.dark .paperclip-markdown a {
|
||
color: color-mix(in oklab, var(--foreground) 80%, #58a6ff 20%);
|
||
}
|
||
|
||
.dark .paperclip-markdown.paperclip-markdown-on-accent :where(a, a:visited) {
|
||
color: currentColor;
|
||
}
|
||
|
||
.paperclip-markdown blockquote {
|
||
margin-left: 0;
|
||
padding-left: 0.95rem;
|
||
border-left: 0.24rem solid color-mix(in oklab, var(--border) 84%, var(--muted-foreground) 16%);
|
||
color: var(--muted-foreground);
|
||
}
|
||
|
||
.paperclip-markdown.paperclip-markdown-on-accent blockquote {
|
||
border-left-color: color-mix(in oklab, currentColor 56%, transparent);
|
||
color: color-mix(in oklab, currentColor 88%, transparent);
|
||
}
|
||
|
||
.paperclip-markdown.paperclip-markdown-on-accent .paperclip-workspace-file-link {
|
||
border-color: color-mix(in oklab, currentColor 34%, transparent);
|
||
background-color: color-mix(in oklab, currentColor 16%, transparent);
|
||
color: currentColor;
|
||
}
|
||
|
||
.paperclip-markdown.paperclip-markdown-on-accent .paperclip-workspace-file-link:hover {
|
||
background-color: color-mix(in oklab, currentColor 24%, transparent);
|
||
}
|
||
|
||
.paperclip-markdown.paperclip-markdown-on-accent :where(a.paperclip-mention-chip, a.paperclip-project-mention-chip) {
|
||
border-color: color-mix(in oklab, currentColor 34%, transparent) !important;
|
||
background: color-mix(in oklab, currentColor 16%, transparent) !important;
|
||
color: currentColor !important;
|
||
}
|
||
|
||
.paperclip-markdown hr {
|
||
margin: 1.25rem 0;
|
||
border-color: var(--border);
|
||
}
|
||
|
||
.paperclip-markdown img {
|
||
border-radius: calc(var(--radius) + 2px);
|
||
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--foreground) 10%, transparent);
|
||
}
|
||
|
||
.paperclip-markdown-table-scroll {
|
||
max-width: 100%;
|
||
overflow-x: auto;
|
||
overscroll-behavior-x: contain;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
|
||
.paperclip-markdown-table-scroll:focus-visible {
|
||
outline: 2px solid var(--ring);
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
.paperclip-markdown-table-scroll table {
|
||
width: max-content;
|
||
min-width: 100%;
|
||
margin: 0;
|
||
}
|
||
|
||
.paperclip-markdown-table-scroll :where(th, td) {
|
||
min-width: 8rem;
|
||
max-width: 18rem;
|
||
vertical-align: top;
|
||
}
|
||
|
||
.paperclip-markdown th {
|
||
font-weight: 600;
|
||
text-align: left;
|
||
}
|
||
|
||
.paperclip-mermaid {
|
||
margin: 0.5rem 0;
|
||
padding: 0.45rem 0.55rem;
|
||
border: 1px solid var(--border);
|
||
border-radius: calc(var(--radius) - 3px);
|
||
background-color: color-mix(in oklab, var(--accent) 35%, transparent);
|
||
overflow-x: auto;
|
||
}
|
||
|
||
.paperclip-mermaid svg {
|
||
display: block;
|
||
width: max-content;
|
||
max-width: none;
|
||
min-width: 100%;
|
||
height: auto;
|
||
}
|
||
|
||
.paperclip-mermaid-status {
|
||
margin: 0 0 0.45rem;
|
||
font-size: 0.75rem;
|
||
color: var(--muted-foreground);
|
||
}
|
||
|
||
.paperclip-mermaid-status-error {
|
||
color: var(--destructive);
|
||
}
|
||
|
||
.paperclip-mermaid-source {
|
||
margin: 0;
|
||
padding: 0;
|
||
border: 0;
|
||
background: transparent;
|
||
}
|
||
|
||
/* Mention chips rendered inline in prose (MarkdownBody or inline anchors) */
|
||
a.paperclip-mention-chip,
|
||
a.paperclip-project-mention-chip,
|
||
span.paperclip-mention-chip,
|
||
span.paperclip-project-mention-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.25rem;
|
||
margin: 0 0.1rem;
|
||
padding: 0 0.625rem;
|
||
border: 1px solid var(--border);
|
||
border-radius: 999px;
|
||
font-size: 0.75rem;
|
||
line-height: 1.25;
|
||
text-decoration: none;
|
||
/* Center the pill on the surrounding x-height so it sits on the text line
|
||
instead of hanging below it. inline-flex baseline alignment is unreliable
|
||
across browsers, so use vertical-align: middle for a predictable result.
|
||
Nudge up 1px so it visually centers with the cap height of the text. */
|
||
vertical-align: middle;
|
||
position: relative;
|
||
top: -1px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* Touch target floor: WCAG 2.5.5 expects ≥24px on coarse pointers. The base
|
||
chip is 22px; bump it on touch viewports without disturbing inline metrics. */
|
||
@media (pointer: coarse) {
|
||
a.paperclip-mention-chip,
|
||
a.paperclip-project-mention-chip,
|
||
span.paperclip-mention-chip,
|
||
span.paperclip-project-mention-chip {
|
||
min-height: 24px;
|
||
}
|
||
}
|
||
|
||
/* External object pills: variant marker the host renders for known-resolved
|
||
external URLs. Inline status icon is rendered by ExternalObjectStatusIcon. */
|
||
a.paperclip-mention-chip[data-mention-kind="external-object"],
|
||
span.paperclip-mention-chip[data-mention-kind="external-object"] {
|
||
/* Inherit base chip styling; status colour comes from inline classes so the
|
||
host can vary tone per category without defining a CSS variant per state. */
|
||
}
|
||
|
||
/* When the identifier inside a chip is backtick-wrapped in markdown, strip the
|
||
inline-code monospace/gray styling so the pill label reads cleanly. */
|
||
.paperclip-markdown a.paperclip-mention-chip code,
|
||
.paperclip-markdown a.paperclip-project-mention-chip code,
|
||
.paperclip-markdown span.paperclip-mention-chip code,
|
||
.paperclip-markdown span.paperclip-project-mention-chip code {
|
||
font-family: inherit;
|
||
background: none;
|
||
color: inherit;
|
||
padding: 0;
|
||
font-size: inherit;
|
||
}
|
||
|
||
/* Keep MDXEditor popups above app dialogs, even when they portal to <body>. */
|
||
[class*="_popupContainer_"] {
|
||
z-index: 81 !important;
|
||
}
|
||
|
||
[class*="_dialogOverlay_"] {
|
||
z-index: 80;
|
||
}
|
||
|
||
[class*="_dialogContent_"],
|
||
[class*="_largeDialogContent_"],
|
||
[class*="_popoverContent_"],
|
||
[class*="_linkDialogPopoverContent_"],
|
||
[class*="_tableColumnEditorPopoverContent_"],
|
||
[class*="_toolbarButtonDropdownContainer_"],
|
||
[class*="_toolbarNodeKindSelectContainer_"] {
|
||
z-index: 81 !important;
|
||
}
|
||
|
||
/* Status chip/fill helpers.
|
||
Each consumer sets a local `--sc` to the status' base hue (e.g.
|
||
`--sc: var(--status-task-in_progress)`); these rules derive the rendered
|
||
fill / text / border from it. `.status-chip` is the bordered chip (agent +
|
||
task chips); `.status-fill` a solid swatch (heartbeat capsule). Light mixes
|
||
the hue toward white for a soft fill; dark layers the hue at low alpha so it
|
||
reads on dark surfaces. A single hue covers both modes. */
|
||
.status-chip {
|
||
background-color: color-mix(in srgb, var(--sc) 15%, white);
|
||
color: color-mix(in srgb, var(--sc) 82%, black);
|
||
border-color: var(--sc);
|
||
}
|
||
.dark .status-chip {
|
||
background-color: color-mix(in srgb, var(--sc) 22%, transparent);
|
||
color: color-mix(in srgb, var(--sc) 80%, white);
|
||
border-color: color-mix(in srgb, var(--sc) 48%, transparent);
|
||
}
|
||
.status-fill {
|
||
background-color: var(--sc);
|
||
}
|
||
|
||
/* ── Extracted verbatim tokens (Phase 2, design/token-extraction) ── */
|
||
/* Batch 1/4: color literals only. Reused-from-existing-token sites (see
|
||
TOKEN-AUDIT.md section 1.1) are NOT duplicated here — they reference
|
||
--status-task-in_progress / --status-task-done directly at the call site.
|
||
|
||
Allowlist (sites intentionally left as hardcoded / functional literals,
|
||
NOT converted to tokens — each also carries an inline
|
||
`token-extraction: allowlisted` comment at the site):
|
||
- pages/CompanyEnvironments.tsx — xterm.js terminal theme config; functional JS values, third-party.
|
||
- pages/CompanySettings.tsx — <input type="color"> value; functional form control, not a rendered value.
|
||
- components/issue-properties/IssueProperties.tsx (newLabelColor) — color-picker seed persisted into label-create payload.
|
||
- pages/CompanySkills.tsx (DISCOVERY_ACCENTS) — persisted/compared skill.color JS data, not just rendered.
|
||
- components/IssueColumns.tsx (accentColor fallback) — also feeds pickTextColorForPillBg() contrast math.
|
||
- components/CompanyPatternIcon.tsx — canvas fillStyle computed at runtime from numeric props, not a static literal.
|
||
- components/FileViewerSheet.tsx — half-migrated var(--paperclip-code-highlight-*, fallback) pattern at the time of Batch 1; RESOLVED in Batch 4 via *-resolved wrapper tokens (see the Batch 4 MISC token block below) — no longer allowlisted.
|
||
- pages/InviteUxLab.tsx (brandColor prop, x2) — demo/showcase-only prop feeding CompanyPatternIcon's hexToHue() color math, not a rendered CSS value.
|
||
*/
|
||
:root {
|
||
--hex-959596: #959596; /* Muted feed actor/verb/title text (ActivityFeed.tsx, FeedCard.tsx) — PRIOR-ART-flagged gap cluster, no existing token match. */
|
||
--hex-1d1d1d: #1d1d1d; /* OnboardingWizard.tsx dark decorative panel background, singleton. */
|
||
--hex-22d3ee: #22d3ee; /* OrgChart.tsx agent status dot — 'running' (independent palette from --status-agent-*, see TOKEN-AUDIT.md 1.2). */
|
||
--hex-4ade80: #4ade80; /* OrgChart.tsx agent status dot — 'active'. */
|
||
--hex-facc15: #facc15; /* OrgChart.tsx agent status dot — 'paused' / 'idle' (shared value). */
|
||
--hex-f87171: #f87171; /* OrgChart.tsx agent status dot — 'error'. */
|
||
--hex-a3a3a3: #a3a3a3; /* OrgChart.tsx agent status dot — 'terminated' + defaultDotColor fallback. */
|
||
--hex-ef4444: #ef4444; /* ActivityCharts.tsx — priority 'critical'; also the <0.5 success-rate bar tint and the run-activity 'failed' segment. (Chart STATUS hues re-pointed at --status-task-* in DECISION-SHEET.md B5; priority/success series stay independent.) */
|
||
--hex-f97316: #f97316; /* ActivityCharts.tsx — priority 'high'. */
|
||
--hex-eab308: #eab308; /* ActivityCharts.tsx — priority 'medium'; also the 0.5-0.8 success-rate bar tint. */
|
||
--hex-6b7280: #6b7280; /* ActivityCharts.tsx — priority 'low' + unknown-status statusColors fallback. */
|
||
--hex-10b981: #10b981; /* ActivityCharts.tsx — >=0.8 success-rate bar tint; also the run-activity 'succeeded' segment. */
|
||
--hex-737373: #737373; /* ActivityCharts.tsx — run activity 'other' segment tint. */
|
||
--project-none: #64748b; /* Semantic rename of --hex-64748b (DECISION-SHEET.md A3, value unchanged). 'No project assigned' muted-slate fallback (TOKEN-AUDIT.md 1.3) across Routines/MarkdownEditor/RoutineRunVariablesDialog/RoutineList/IssueColumns/editable-sections; also ActivityCharts.tsx status 'backlog'. */
|
||
--project-seed: #6366f1; /* Semantic rename of --hex-6366f1 (DECISION-SHEET.md A3, value unchanged). Project-color-fallback indigo seed default (ProjectDetail/PipelineSettings/IssueProperties/NewIssueDialog) — new-project-color-picker-seed family per TOKEN-AUDIT.md 1.3. */
|
||
--liveness-blue: #2563eb; /* DECISION-SHEET.md A6: IssueChatThread.tsx human-message 'liveness blue' bubble (PAP-95 rev 5). Same value as --status-task-in_progress by coincidence, decoupled on purpose so a future status-hue change doesn't drag the chat bubble along. */
|
||
--app-logo-tile-1: #ff4f00;
|
||
--app-logo-tile-2: #24292f;
|
||
--app-logo-tile-3: #4a154b;
|
||
--app-logo-tile-4: #0f172a;
|
||
--app-logo-tile-5: #5e6ad2;
|
||
--app-logo-tile-6: #1a73e8;
|
||
--app-logo-tile-7: #0f9d58;
|
||
--app-logo-tile-8: #ea4335;
|
||
--gradient-extract-1: linear-gradient(180deg,rgba(255,80,80,0.12),rgba(255,255,255,0.02)); /* Dashboard.tsx budget-alert card gradient. */
|
||
--gradient-extract-2: linear-gradient(180deg,rgba(255,255,255,0.05),rgba(255,255,255,0.02)); /* Costs.tsx subtle white card gradient. */
|
||
--gradient-extract-3: radial-gradient(circle at top left,rgba(244,114,182,0.08),transparent 35%),radial-gradient(circle at bottom right,rgba(56,189,248,0.1),transparent 32%); /* AccountingModelCard.tsx decorative overlay. */
|
||
--gradient-extract-4: linear-gradient(180deg,rgba(255,70,70,0.10),rgba(255,255,255,0.02)); /* BudgetIncidentCard.tsx incident-card gradient. */
|
||
--gradient-extract-7: radial-gradient(circle at top right,rgba(255,255,255,0.22),transparent 34%),radial-gradient(circle at bottom left,rgba(255,255,255,0.08),transparent 36%); /* ProfileSettings.tsx:162 decorative overlay. */
|
||
}
|
||
|
||
/* ── Extracted verbatim TYPE tokens (Phase 2 Batch 2, design/token-extraction) ── */
|
||
/* Batch 2/4: font-size + letter-spacing + line-height literals, verbatim
|
||
(no normalizing — 9/10/11/12/13/14/15px and 0.08-0.24em all stay distinct;
|
||
the human scale-collapse decision comes later per DESIGN.md/TOKEN-AUDIT.md).
|
||
|
||
Allowlist (sites intentionally left as hardcoded / functional literals,
|
||
NOT converted to tokens — each also carries an inline
|
||
`token-extraction: allowlisted` comment at the site):
|
||
- pages/CompanyEnvironments.tsx (fontSize: 12) — xterm.js terminal theme
|
||
config; functional third-party numeric option, not a rendered CSS value.
|
||
Same allowlisted object as Batch 1's color entry for this file.
|
||
- pages/CompanySkills.tsx (fontSize: Math.round(size * 0.42)) — computed at
|
||
runtime from a prop; not a static literal, nothing to extract.
|
||
*/
|
||
:root {
|
||
/* ── Named type ladder (DECISION-SHEET.md B3, preset-tune session) ──
|
||
Collapses the 8 verbatim --fs-* font-size tokens and 9 --ls-*
|
||
letter-spacing tokens (extracted in Batch 2 above) into named steps.
|
||
Codemod: scripts/codemod-type-ladder.mjs. Mapping:
|
||
9px + 10px -> --text-nano (9 -> 10 bump per locked ladder)
|
||
11px + 0.7rem -> --text-micro (0.7rem = 11.2px -> 11px)
|
||
12px -> Tailwind `text-xs` class (12px exact match; scale
|
||
class preferred over a redundant token per DESIGN.md)
|
||
13px -> --text-compact (PRIOR-ART named this tier "sm 13",
|
||
but that collides with Tailwind text-sm = 14px,
|
||
hence "compact")
|
||
14px + 15px -> Tailwind `text-sm` class (14px; 15 -> 14)
|
||
Letter-spacing, nearest-step mapping:
|
||
0.08em, 0.1em -> --tracking-label
|
||
0.12em, 0.14em, 0.16em -> --tracking-eyebrow
|
||
0.18em, 0.2em, 0.22em, 0.24em -> --tracking-caps
|
||
NOTE: sites moved to text-xs/text-sm also pick up the Tailwind scale
|
||
line-height (text-(length:--x) set font-size only) — intentional,
|
||
reviewed on the post-preset contact sheet. */
|
||
--text-nano: 10px;
|
||
--text-micro: 11px;
|
||
--text-compact: 13px;
|
||
--tracking-label: 0.08em;
|
||
--tracking-eyebrow: 0.14em;
|
||
--tracking-caps: 0.2em;
|
||
}
|
||
|
||
/* ── Extracted verbatim SIZE/SPACING/RADIUS/SHADOW tokens (Phase 2 Batch 3, design/token-extraction) ── */
|
||
/* Batch 3/4: width/height/min/max, padding/margin/gap/inset/translate,
|
||
radius, ring/outline-width, and shadow literals, verbatim (no
|
||
normalizing — the human scale-collapse decision comes later per
|
||
DESIGN.md/TOKEN-AUDIT.md). --sz-* is ONE shared family across
|
||
w/h/p/m/gap/inset/translate so identical values dedupe regardless of
|
||
which property used them. --rad-* is likewise shared across
|
||
rounded, ring, and outline widths.
|
||
|
||
Allowlist (sites intentionally left as hardcoded / functional literals
|
||
or var()-only passthrough with no new token minted):
|
||
- components/ui/scroll-area.tsx (rounded-[inherit]) — CSS keyword, not a
|
||
literal value; nothing to extract.
|
||
- Bracket values that only wrap var(--radix-*-trigger-width/height) or
|
||
var(--new-issue-dialog-height) etc. are rewritten to the bare paren
|
||
form directly (e.g. w-(--radix-popover-trigger-width)) — these are
|
||
runtime library/component variables, not design values, so no new
|
||
--sz-* token is minted for them (see TOKEN-AUDIT.md extraction log
|
||
for the full site list).
|
||
*/
|
||
:root {
|
||
--sz-320px: 320px; /* Extracted from ui/src/components/ActiveAgentsPanel.tsx (h-[320px]). */
|
||
--sz-390px: 390px;
|
||
--sz-64px: 64px;
|
||
--sz-3px: 3px; /* Extracted from ui/src/components/ActivityCharts.tsx (gap-[3px]). */
|
||
--sz-calc-1: calc(100% - 1.5rem); /* Extracted from ui/src/components/ActivityFeed.tsx (w-[calc(100%-1.5rem)]). */
|
||
--sz-calc-2: calc(100% - 0.75rem); /* Extracted from ui/src/components/ActivityFeed.tsx (w-[calc(100%-0.75rem)]). */
|
||
--sz-16rem: 16rem; /* Extracted from ui/src/components/ActivityFeed.tsx (max-w-[16rem]). */
|
||
--sz-20rem: 20rem; /* AgentSkillReleasePicker.tsx release menu width cap. */
|
||
--sz-18px: 18px; /* Extracted from ui/src/components/ActivityFeed.tsx (p-[18px]). */
|
||
--sz-44px: 44px; /* Extracted from ui/src/components/AgentConfigForm.tsx (min-h-[44px]). */
|
||
--sz-88px: 88px; /* Extracted from ui/src/components/AgentConfigForm.tsx (min-h-[88px]). */
|
||
--sz-240px: 240px; /* Extracted from ui/src/components/AgentConfigForm.tsx (max-h-[240px]). */
|
||
--sz-10_5rem: 10.5rem; /* Extracted from ui/src/components/BlockedInboxView.tsx (w-[10.5rem]). */
|
||
--sz-150px: 150px; /* Extracted from ui/src/components/BlockedInboxView.tsx (w-[150px]). */
|
||
--sz-5_75rem: 5.75rem; /* Extracted from ui/src/components/BlockedInboxView.tsx (w-[5.75rem]). */
|
||
--sz-12rem: 12rem; /* Extracted from ui/src/components/BlockedInboxView.tsx (max-w-[12rem]). */
|
||
--sz-200px: 200px; /* Extracted from ui/src/components/ChatComposer.test.tsx (max-h-[200px]). */
|
||
--sz-22px: 22px; /* Extracted from ui/src/components/ChatComposer.test.tsx (max-h-[22px]). */
|
||
--sz-60px: 60px; /* Extracted from ui/src/components/CommentThread.tsx (min-h-[60px]). */
|
||
--sz-220px: 220px; /* Extracted from ui/src/components/CompanySwitcher.tsx (w-[220px]). */
|
||
--sz-88vh: 88vh; /* Extracted from ui/src/components/DocumentAnnotationPanel.tsx (max-h-[88vh]). */
|
||
--sz-80vh: 80vh; /* Extracted from ui/src/components/DocumentAnnotationPanel.tsx (max-h-[80vh]). */
|
||
--sz-360px: 360px; /* Extracted from ui/src/components/DocumentAnnotationPanel.tsx (w-[360px]). */
|
||
--sz-85vh: 85vh; /* Extracted from ui/src/components/DocumentDiffModal.tsx (max-h-[85vh]). */
|
||
--sz-6rem: 6rem; /* Extracted from ui/src/components/EntityRow.tsx (min-w-[6rem]). */
|
||
--sz-1px: 1px; /* Extracted from ui/src/components/EntityRow.tsx (top-[1px]). */
|
||
--sz-100px: 100px; /* Extracted from ui/src/components/ExecutionParticipantPicker.tsx (max-w-[100px]). */
|
||
--sz-calc-3: min(840px,calc(100dvh - 2rem)); /* Extracted from ui/src/components/FileViewerSheet.tsx (h-[min(840px,calc(100dvh-2rem))]). */
|
||
--sz-calc-4: calc(100vw - 2rem); /* Extracted from ui/src/components/FileViewerSheet.tsx (w-[calc(100vw-2rem)]). */
|
||
--sz-calc-5: min(1280px,calc(100vw - 2rem)); /* Extracted from ui/src/components/FileViewerSheet.tsx (max-w-[min(1280px,calc(100vw-2rem))]). */
|
||
--sz-94vw: 94vw; /* Extracted from ui/src/components/FileViewerSheet.tsx (w-[94vw]). */
|
||
--sz-1280px: 1280px; /* Extracted from ui/src/components/FileViewerSheet.tsx (max-w-[1280px]). */
|
||
--sz-60vh: 60vh; /* Extracted from ui/src/components/FrontDoor.tsx (min-h-[60vh]). */
|
||
--sz-52vh: 52vh;
|
||
--sz-40rem: 40rem;
|
||
--sz-44rem: 44rem;
|
||
--sz-30pct: 30%;
|
||
--sz-24pct: 24%;
|
||
--sz-18pct: 18%;
|
||
--sz-12pct: 12%;
|
||
--sz-calc-6: min(20rem,calc(100vw - 2rem)); /* Extracted from ui/src/components/InlineEntitySelector.tsx (w-[min(20rem,calc(100vw-2rem))]). */
|
||
--sz-18rem: 18rem; /* Extracted from ui/src/components/IssueBlockedNotice.tsx (max-w-[18rem]). */
|
||
--sz-70vh: 70vh; /* Extracted from ui/src/components/IssueChatThread.test.tsx (max-h-[70vh]). */
|
||
--sz-calc-7: calc(100% - 0.5rem); /* Extracted from ui/src/components/IssueChatThread.test.tsx (max-w-[calc(100%-0.5rem)]). */
|
||
--sz-28dvh: 28dvh; /* Extracted from ui/src/components/IssueChatThread.test.tsx (max-h-[28dvh]). */
|
||
--sz-72px: 72px; /* Extracted from ui/src/components/IssueChatThread.test.tsx (min-h-[72px]). */
|
||
--sz-15px: 15px; /* Extracted from ui/src/components/IssueChatThread.test.tsx (p-[15px]). */
|
||
--sz-calc-8: calc(env(safe-area-inset-bottom) + 20px); /* Extracted from ui/src/components/IssueChatThread.test.tsx (bottom-[calc(env(safe-area-inset-bottom)+20px)]). */
|
||
--sz-300px: 300px; /* Extracted from ui/src/components/IssueColumns.tsx (w-[300px]). */
|
||
--sz-calc-9: min(80vh,42rem); /* Extracted from ui/src/components/IssueFiltersPopover.test.tsx (max-h-[min(80vh,42rem)]). */
|
||
--sz-calc-10: min(780px,calc(100vw - 2rem)); /* Extracted from ui/src/components/IssueFiltersPopover.tsx (w-[min(780px,calc(100vw-2rem))]). */
|
||
--sz-24ch: 24ch; /* Extracted from ui/src/components/IssuePlanDecompositionsSection.tsx (max-w-[24ch]). */
|
||
--sz-calc-13: calc(0.75rem + 0.5rem); /* Extracted from ui/src/components/IssueRow.tsx (ml-[calc(theme(spacing.3)+theme(spacing.2))]). */
|
||
--sz-140px: 140px; /* Extracted from ui/src/components/JsonSchemaForm.tsx (min-h-[140px]). */
|
||
--sz-52px: 52px; /* Extracted from ui/src/components/KanbanBoard.tsx (w-[52px]). */
|
||
--sz-48px: 48px; /* Extracted from ui/src/components/KanbanBoard.tsx (min-w-[48px]). */
|
||
--sz-260px: 260px; /* Extracted from ui/src/components/KanbanBoard.tsx (min-w-[260px]). */
|
||
--sz-120px: 120px; /* Extracted from ui/src/components/KanbanBoard.tsx (min-h-[120px]). */
|
||
--sz-safe-top: env(safe-area-inset-top); /* Extracted from ui/src/components/Layout.tsx (pt-[env(safe-area-inset-top)]). */
|
||
--sz-calc-14: calc(5rem + env(safe-area-inset-bottom)); /* Extracted from ui/src/components/Layout.tsx (pb-[calc(5rem+env(safe-area-inset-bottom))]). */
|
||
--sz-38ch: 38ch; /* Extracted from ui/src/components/MarkdownBody.test.tsx (max-w-[38ch]). */
|
||
--sz-208px: 208px; /* Extracted from ui/src/components/MarkdownEditor.test.tsx (max-h-[208px]). */
|
||
--sz-180px: 180px; /* Extracted from ui/src/components/MarkdownEditor.tsx (min-w-[180px]). */
|
||
--sz-calc-15: calc(100vw - 16px); /* Extracted from ui/src/components/MarkdownEditor.tsx (max-w-[calc(100vw-16px)]). */
|
||
--sz-66px: 66px; /* Extracted from ui/src/components/MembershipAction.tsx (w-[66px]). */
|
||
--sz-safe-bottom: env(safe-area-inset-bottom); /* Extracted from ui/src/components/MobileBottomNav.tsx (pb-[env(safe-area-inset-bottom)]). */
|
||
--sz-calc-16: min(calc(100dvh - 2rem),46rem); /* Extracted from ui/src/components/NewAgentDialog.tsx (max-h-[min(calc(100dvh-2rem),46rem)]). */
|
||
--sz-24rem: 24rem; /* Extracted from ui/src/components/NewAgentDialog.tsx (h-[24rem]). */
|
||
--sz-50vh: 50vh; /* Extracted from ui/src/components/NewGoalDialog.tsx (max-h-[50vh]). */
|
||
--sz-8_5rem: 8.5rem; /* Extracted from ui/src/components/NewIssueDialog.tsx (min-w-[8.5rem]). */
|
||
--sz-160px: 160px; /* Extracted from ui/src/components/NewProjectDialog.tsx (max-w-[160px]). */
|
||
--sz-80px: 80px; /* Extracted from ui/src/components/OnboardingWizard.tsx (min-h-[80px]). */
|
||
--sz-calc-17: calc(100vh - 4rem); /* Extracted from ui/src/components/PageSkeleton.tsx (h-[calc(100vh-4rem)]). */
|
||
--sz-2px: 2px; /* Extracted from ui/src/components/QuotaBar.tsx (w-[2px]). */
|
||
--sz-calc-18: calc(100dvh - 2rem); /* Extracted from ui/src/components/RoutineRunVariablesDialog.test.tsx (max-h-[calc(100dvh-2rem)]). */
|
||
--sz-calc-19: calc(1rem + env(safe-area-inset-bottom)); /* Extracted from ui/src/components/RoutineRunVariablesDialog.test.tsx (pb-[calc(1rem+env(safe-area-inset-bottom))]). */
|
||
--sz-calc-20: min(calc(100dvh - 2rem),42rem); /* Extracted from ui/src/components/RoutineRunVariablesDialog.tsx (max-h-[min(calc(100dvh-2rem),42rem)]). */
|
||
--sz-100dvh: 100dvh; /* Extracted from ui/src/components/RoutineSubSidebar.tsx (max-h-[100dvh]). */
|
||
--sz-calc-21: calc(1.5rem + 5rem + env(safe-area-inset-bottom)); /* Extracted from ui/src/components/ScrollToBottom.tsx (bottom-[calc(1.5rem+5rem+env(safe-area-inset-bottom))]). */
|
||
--sz-calc-22: calc(320px + 1.5rem); /* Extracted from ui/src/components/ScrollToBottom.tsx (right-[calc(320px+1.5rem)]). */
|
||
--sz-calc-23: min(32rem,calc(100vw - 2rem)); /* Extracted from ui/src/components/SearchableSelect.tsx (max-w-[min(32rem,calc(100vw-2rem))]). */
|
||
--sz-277px: 277px; /* Extracted from ui/src/components/SidebarAccountMenu.test.tsx (w-[277px]). */
|
||
--sz-calc-24: calc(100vw - 1rem); /* Extracted from ui/src/components/SidebarAccountMenu.tsx (max-w-[calc(100vw-1rem)]). */
|
||
--sz-4_5rem: 4.5rem; /* Extracted from ui/src/components/SidebarAgents.tsx (min-w-[4.5rem]). */
|
||
--sz-36px: 36px; /* Extracted from ui/src/components/WorkspaceFileBrowser.tsx (min-h-[36px]). */
|
||
--sz-32px: 32px; /* Extracted from ui/src/components/WorkspaceFileBrowser.tsx (min-h-[32px]). */
|
||
--sz-30px: 30px; /* Extracted from ui/src/components/WorkspaceFileBrowser.tsx (min-h-[30px]). */
|
||
--sz-8px: 8px; /* Extracted from ui/src/components/artifacts/ArtifactGroupCard.tsx (translate-x-[8px]). */
|
||
--sz-4px: 4px; /* Extracted from ui/src/components/artifacts/ArtifactGroupCard.tsx (translate-x-[4px]). */
|
||
--sz-34px: 34px; /* Extracted from ui/src/components/environment-variables-editor/SecretPicker.tsx (h-[34px]). */
|
||
--sz-11rem: 11rem; /* Extracted from ui/src/components/issue-properties/IssueProperties.tsx (min-w-[11rem]). */
|
||
--sz-32rem: 32rem; /* Extracted from ui/src/components/issue-properties/IssueProperties.tsx (w-[32rem]). */
|
||
--sz-280px: 280px; /* Extracted from ui/src/components/timeline/WorkTimelineChart.tsx (max-w-[280px]). */
|
||
--sz-7px: 7px; /* Extracted from ui/src/components/transcript/RunTranscriptView.tsx (mt-[7px]). */
|
||
--sz-calc-25: calc(100% - 2rem); /* Extracted from ui/src/components/ui/alert-dialog.tsx (max-w-[calc(100%-2rem)]). */
|
||
--sz-calc-26: max(1rem,env(safe-area-inset-top)); /* Extracted from ui/src/components/ui/alert-dialog.tsx (top-[max(1rem,env(safe-area-inset-top))]). */
|
||
--sz-8rem: 8rem; /* Extracted from ui/src/components/ui/dropdown-menu.tsx (min-w-[8rem]). */
|
||
--sz-calc-27: calc(100% - 1px); /* Extracted from ui/src/components/ui/tabs.tsx (h-[calc(100%-1px)]). */
|
||
--sz-neg-5px: -5px; /* Extracted from ui/src/components/ui/tabs.tsx (bottom-[-5px]). */
|
||
--sz-calc-28: calc(-50% - 2px); /* Extracted from ui/src/components/ui/tooltip.tsx (translate-y-[calc(-50%_-_2px)]). */
|
||
--sz-420px: 420px; /* Extracted from ui/src/pages/AgentDetail.tsx (min-h-[420px]). */
|
||
--sz-30rem: 30rem; /* Extracted from ui/src/pages/AgentDetail.tsx (w-[30rem]). */
|
||
--sz-38rem: 38rem; /* Extracted from ui/src/pages/AgentDetail.tsx (max-h-[38rem]). */
|
||
--sz-7rem: 7rem; /* Extracted from ui/src/pages/Agents.test.tsx (min-w-[7rem]). */
|
||
--sz-calc-29: calc(100% + 3rem); /* Extracted from ui/src/pages/BoardChat.tsx (h-[calc(100%+3rem)]). */
|
||
--sz-22rem: 22rem; /* Extracted from ui/src/pages/CompanyEnvironments.tsx (h-[22rem]). */
|
||
--sz-560px: 560px; /* Extracted from ui/src/pages/CompanyExport.tsx (min-h-[560px]). */
|
||
--sz-520px: 520px; /* Extracted from ui/src/pages/CompanyExport.tsx (min-h-[520px]). */
|
||
--sz-480px: 480px; /* Extracted from ui/src/pages/CompanyExport.tsx (max-h-[480px]). */
|
||
--sz-calc-30: calc(100vh - 12rem); /* Extracted from ui/src/pages/CompanyExport.tsx (h-[calc(100vh-12rem)]). */
|
||
--sz-calc-31: calc(100vh - 16rem); /* Extracted from ui/src/pages/CompanyImport.tsx (h-[calc(100vh-16rem)]). */
|
||
--sz-calc-32: min(28rem,70vh); /* Extracted from ui/src/pages/CompanySkills.tsx (max-h-[min(28rem,70vh)]). */
|
||
--sz-11_5rem: 11.5rem; /* Extracted from ui/src/pages/CompanySkills.tsx (min-h-[11.5rem]). */
|
||
--sz-11_75rem: 11.75rem; /* SkillStudio.tsx file/input browser cap: five min-h-9 rows plus p-1 vertical padding. */
|
||
--sz-calc-33: calc(100dvh - 6rem); /* Extracted from ui/src/pages/CompanySkills.tsx (h-[calc(100dvh-6rem)]). */
|
||
--sz-calc-34: clamp(14rem,45vh,28rem); /* Extracted from ui/src/pages/CompanySkills.tsx (h-[clamp(14rem,45vh,28rem)]). */
|
||
--sz-400px: 400px; /* Extracted from ui/src/pages/CompanySkills.tsx (min-h-[400px]). */
|
||
--sz-calc-35: min(34rem,55vw); /* Extracted from ui/src/pages/CompanySkills.tsx (max-w-[min(34rem,55vw)]). */
|
||
--sz-640px: 640px; /* Extracted from ui/src/pages/DesignGuide.tsx (max-w-[640px]). */
|
||
--sz-28rem: 28rem; /* Extracted from ui/src/pages/DesignGuide.tsx (w-[28rem]). */
|
||
--sz-170px: 170px; /* Extracted from ui/src/pages/Inbox.tsx (w-[170px]). */
|
||
--sz-calc-36: min(28rem,65vh); /* Extracted from ui/src/pages/InstanceExperimentalSettings.tsx (max-h-[min(28rem,65vh)]). */
|
||
--sz-calc-37: min(100%,22rem); /* Extracted from ui/src/pages/IssueDetail.tsx (w-[min(100%,22rem)]). */
|
||
--sz-85dvh: 85dvh; /* Extracted from ui/src/pages/IssueDetail.tsx (max-h-[85dvh]). */
|
||
--sz-calc-38: calc(100dvh - 9rem); /* Extracted from ui/src/pages/OrgChart.tsx (h-[calc(100dvh-9rem)]). */
|
||
--sz-calc-39: calc(100dvh - 3.5rem); /* Extracted from ui/src/pages/PipelineSettings.tsx (max-h-[calc(100dvh-3.5rem)]). */
|
||
--sz-calc-40: min(24rem,calc(100vw - 2rem)); /* Extracted from ui/src/pages/PipelineSettings.tsx (w-[min(24rem,calc(100vw-2rem))]). */
|
||
--sz-780px: 780px; /* Extracted from ui/src/pages/Pipelines.tsx (min-w-[780px]). */
|
||
--sz-148px: 148px; /* Extracted from ui/src/pages/Pipelines.tsx (w-[148px]). */
|
||
--sz-11px: 11px; /* Extracted from ui/src/pages/Pipelines.tsx (h-[11px]). */
|
||
--sz-9px: 9px; /* Extracted from ui/src/pages/Pipelines.tsx (h-[9px]). */
|
||
--sz-720px: 720px; /* Extracted from ui/src/pages/RunTranscriptUxLab.tsx (max-h-[720px]). */
|
||
--sz-460px: 460px; /* Extracted from ui/src/pages/RunTranscriptUxLab.tsx (max-h-[460px]). */
|
||
--sz-960px: 960px; /* Extracted from ui/src/pages/Search.tsx (max-w-[960px]). */
|
||
--sz-70px: 70px; /* Extracted from ui/src/pages/Secrets.tsx (min-h-[70px]). */
|
||
--sz-calc-41: min(520px,calc(100vw - 2rem)); /* Extracted from ui/src/pages/Secrets.tsx (w-[min(520px,calc(100vw-2rem))]). */
|
||
--sz-calc-42: min(80vh,34rem); /* Extracted from ui/src/pages/Secrets.tsx (max-h-[min(80vh,34rem)]). */
|
||
--sz-14rem: 14rem; /* Extracted from ui/src/pages/TeamCatalog.tsx (max-w-[14rem]). */
|
||
--rad-3: 3px; /* Extracted from ui/src/components/ExternalObjectPill.tsx (ring-[3px]). */
|
||
--rad-4: 4px; /* Extracted from ui/src/components/IssueChatThread.tsx (rounded-br-[4px]). */
|
||
--rad-2: 2px; /* Extracted from ui/src/components/SidebarCompanyMenu.tsx (ring-[2px]). */
|
||
--rad-14: 14px; /* Extracted from ui/src/pages/CompanySettings.tsx (rounded-[14px]). */
|
||
--rad-28: 28px; /* Extracted from ui/src/pages/InviteUxLab.tsx (rounded-[28px]). */
|
||
--rad-24: 24px; /* Extracted from ui/src/pages/InviteUxLab.tsx (rounded-[24px]). */
|
||
--rad-32: 32px; /* Extracted from ui/src/pages/InviteUxLab.tsx (rounded-[32px]). */
|
||
--pct-90: 90%; /* Extracted from ui/src/components/DocumentDiffModal.tsx (max-w-[90%]). */
|
||
--pct-50: 50%; /* Extracted from ui/src/components/ImageGalleryModal.tsx (max-w-[50%]). */
|
||
--pct-85: 85%; /* Extracted from ui/src/components/IssueChatThread.test.tsx (max-w-[85%]). */
|
||
--pct-70: 70%; /* Extracted from ui/src/components/CaseFieldsPanel.tsx (max-w-[70%]). */
|
||
--pct-neg-50: -50%; /* Extracted from ui/src/components/ui/alert-dialog.tsx (translate-x-[-50%]). */
|
||
--pct-72: 72%; /* Extracted from ui/src/pages/IssueDetail.tsx (w-[72%]). */
|
||
--shadow-extract-1: 0 16px 40px rgba(37,99,235,0.08); /* ActiveAgentsPanel.tsx live-box glow. Gallery feedback r2: liveness glow recolored cyan->status blue (value edit, site unchanged; originally extracted as rgba(6,182,212,0.08)). */
|
||
--shadow-extract-2: 0 20px 80px -40px rgba(0,0,0,0.55); /* Extracted from ui/src/components/BudgetPolicyCard.tsx (shadow-[0_20px_80px_-40px_rgba(0,0,0,0.55)]). */
|
||
--shadow-extract-3: 0 0 0 1px rgba(255,255,255,0.08); /* Extracted from ui/src/components/BudgetSidebarMarker.tsx (shadow-[0_0_0_1px_rgba(255,255,255,0.08)]). */
|
||
--shadow-extract-4: 0 -12px 28px rgba(15,23,42,0.08); /* Extracted from ui/src/components/ChatComposer.test.tsx (shadow-[0_-12px_28px_rgba(15,23,42,0.08)]). */
|
||
--shadow-extract-5: 0 -12px 28px rgba(0,0,0,0.28); /* Extracted from ui/src/components/ChatComposer.test.tsx (shadow-[0_-12px_28px_rgba(0,0,0,0.28)]). */
|
||
--shadow-extract-6: 0 0 0 1px var(--color-background); /* Extracted from ui/src/components/DocumentAnnotationLayer.tsx (shadow-[0_0_0_1px_var(--color-background)]). */
|
||
--shadow-extract-7: 0 -12px 28px rgba(15,23,42,0.08),0 0 0 1px hsl(var(--primary)/0.16); /* Extracted from ui/src/components/IssueChatThread.tsx (shadow-[0_-12px_28px_rgba(15,23,42,0.08),0_0_0_1px_hsl(var(--primary)/0.16)]). */
|
||
--shadow-extract-8: 0 1px 0 rgba(15,23,42,0.02); /* Extracted from ui/src/components/IssueRecoveryActionCard.tsx (shadow-[0_1px_0_rgba(15,23,42,0.02)]). */
|
||
--shadow-extract-9: 0 18px 42px rgba(15,23,42,0.06); /* Extracted from ui/src/components/IssueThreadInteractionCard.tsx (shadow-[0_18px_42px_rgba(15,23,42,0.06)]). */
|
||
--shadow-extract-10: 0 1px 0 1px hsl(var(--border)); /* Extracted from ui/src/components/KeyboardShortcutsCheatsheet.tsx (shadow-[0_1px_0_1px_hsl(var(--border))]). */
|
||
--shadow-extract-11: 0 18px 50px rgba(37,99,235,0.08); /* LiveRunWidget.tsx glow. Gallery feedback r2: liveness glow recolored cyan->status blue (value edit, site unchanged; originally extracted as rgba(6,182,212,0.08)). */
|
||
--shadow-extract-12: 0 0 0 2px hsl(var(--background)); /* Extracted from ui/src/components/SidebarNavItem.tsx (shadow-[0_0_0_2px_hsl(var(--background))]). */
|
||
--shadow-extract-13: 0 0 0 3px rgba(245,158,11,0.18); /* Extracted from ui/src/components/environment-variables-editor/index.tsx (shadow-[0_0_0_3px_rgba(245,158,11,0.18)]). */
|
||
--shadow-extract-14: 0 0 12px rgba(37,99,235,0.08); /* AgentDetail.tsx live-card glow. Gallery feedback r2: liveness glow recolored cyan->status blue (value edit, site unchanged; originally extracted as rgba(6,182,212,0.08)). */
|
||
--shadow-extract-18: 0 18px 44px -28px rgba(0,0,0,0.45); /* Extracted from ui/src/pages/ProfileSettings.tsx (shadow-[0_18px_44px_-28px_rgba(0,0,0,0.45)]). */
|
||
}
|
||
|
||
/* ── Extracted verbatim MISC tokens (Phase 2 Batch 4, design/token-extraction) ── */
|
||
/* Batch 4/4 (final sweep): grid track lists, transition-property
|
||
lists, z-index, scale, easing, vertical-align, stroke-width, blur,
|
||
drop-shadow, and half-migrated var(x, fallback) color forms,
|
||
verbatim (no normalizing - the human scale-collapse decision comes
|
||
later per DESIGN.md/TOKEN-AUDIT.md). --gtc-* and --gtr-* and --tp-* are
|
||
sequentially numbered / slugged since their content (track lists,
|
||
property lists) is not safely nameable by value alone.
|
||
|
||
drop-shadow (a CSS filter function) gets its own
|
||
--drop-shadow-extract-* family, kept separate from Batch 3's
|
||
--shadow-extract-* box-shadow family per that batch's logged
|
||
human-decision note.
|
||
|
||
*-resolved wrapper tokens hold a verbatim var(--x, fallback)
|
||
expression for sites where Tailwind v4's paren-with-fallback-comma
|
||
shorthand does not parse (confirmed unsupported in this batch's
|
||
Step 0 syntax spike). theme(colors.a.b) fallbacks were resolved to
|
||
the equivalent var(--token) form the Tailwind build already
|
||
compiles them to today (inspected byte-for-byte from the built
|
||
storybook-static CSS before this codemod ran: theme(colors.muted.DEFAULT)
|
||
-> var(--muted), theme(colors.muted.foreground) -> var(--muted-foreground)).
|
||
|
||
--gradient-extract-* here CONTINUES Batch 1's counter (Batch 1 minted
|
||
1 through 24) rather than restarting it - these 2 sites use
|
||
hsl(var(...))/color-mix(...) CSS-native color functions rather than raw
|
||
hex/rgb literals, which is why Batch 1's hand-audited color-literal
|
||
table did not catch them, but they are still value-bearing gradient
|
||
brackets in scope for this batch's final sweep.
|
||
|
||
Allowlist (sites intentionally left as-is - see ALLOWLIST doc-comment
|
||
at the end of this file for the machine-readable list consumed by
|
||
scripts/check-token-gates.mjs):
|
||
allow ui/src/components/ui/dialog.tsx — tw-animate-css plugin utilities (zoom-in-[0.97] etc.) are dead/no-op classes today (plugin not installed, verified via built-CSS grep); nothing to tokenize without visually changing a currently-inert class
|
||
allow ui/src/components/ui/alert-dialog.tsx — same tw-animate-css dead-class situation as dialog.tsx
|
||
*/
|
||
:root {
|
||
--gtc-1: 56px 56px 24px minmax(0,1fr); /* Extracted from ui/src/components/DocumentDiffModal.tsx (grid-cols-[56px_56px_24px_minmax(0,1fr)]). */
|
||
--gtc-case-revisions: 16rem 1fr; /* Extracted from ui/src/components/CaseRevisionRail.tsx (grid-cols-[16rem_1fr]). */
|
||
--gtc-2: auto minmax(0,1fr) 2.25rem; /* Extracted from ui/src/components/FileTree.tsx (grid-cols-[auto_minmax(0,1fr)_2.25rem]). */
|
||
--gtc-3: minmax(0,1fr) 2.25rem; /* Extracted from ui/src/components/FileTree.tsx (grid-cols-[minmax(0,1fr)_2.25rem]). */
|
||
--gtc-4: minmax(0,1fr); /* Extracted from ui/src/components/FileTree.tsx (grid-cols-[minmax(0,1fr)]). */
|
||
--gtc-5: auto minmax(0,1fr); /* Extracted from ui/src/components/FileViewerSheet.tsx (grid-cols-[auto_minmax(0,1fr)]). */
|
||
--gtc-6: auto minmax(0,1fr) auto; /* Extracted from ui/src/components/FileViewerSheet.tsx (grid-cols-[auto_minmax(0,1fr)_auto]). */
|
||
--gtc-7: 7.5rem minmax(0,1fr); /* Extracted from ui/src/components/IssueChatThread.tsx (grid-cols-[7.5rem_minmax(0,1fr)]). */
|
||
--gtc-8: 7.5rem 1fr; /* Extracted from ui/src/components/IssueRecoveryActionCard.tsx (grid-cols-[7.5rem_1fr]). */
|
||
--gtc-9: 300px minmax(0,1fr); /* Extracted from ui/src/components/RoutineHistoryTab.tsx (grid-cols-[300px_minmax(0,1fr)]). */
|
||
--gtc-10: 10rem 1fr; /* Extracted from ui/src/components/SourceResolvedFoldCallout.tsx (grid-cols-[10rem_1fr]). */
|
||
--gtc-11: 12rem 1fr; /* Extracted from ui/src/components/SourceResolvedFoldCallout.tsx (grid-cols-[12rem_1fr]). */
|
||
--gtc-12: minmax(0,0.75fr) minmax(0,1fr); /* Extracted from ui/src/components/TrustPresetSection.tsx (grid-cols-[minmax(0,0.75fr)_minmax(0,1fr)]). */
|
||
--gtc-13: minmax(0,1fr) auto; /* Extracted from ui/src/components/environment-variables-editor/Row.tsx (grid-cols-[minmax(0,1fr)_auto]). */
|
||
--gtc-14: minmax(160px,2fr) minmax(240px,3fr) 32px; /* Extracted from ui/src/components/environment-variables-editor/Row.tsx (grid-cols-[minmax(160px,2fr)_minmax(240px,3fr)_32px]). */
|
||
--gtc-15: 6rem 1fr; /* Extracted from ui/src/components/issue-properties/IssueProperties.tsx (grid-cols-[6rem_1fr]). */
|
||
--gtc-16: auto 1fr; /* Extracted from ui/src/components/transcript/RunTranscriptView.tsx (grid-cols-[auto_1fr]). */
|
||
--gtc-17: 1fr auto; /* Extracted from ui/src/components/ui/card.tsx (grid-cols-[1fr_auto]). */
|
||
--gtc-18: auto minmax(0,1fr) minmax(12rem,0.65fr); /* Extracted from ui/src/pages/AgentDetail.tsx (grid-cols-[auto_minmax(0,1fr)_minmax(12rem,0.65fr)]). */
|
||
--gtc-19: 260px minmax(0,1fr); /* Extracted from ui/src/pages/AgentDetail.tsx (grid-cols-[260px_minmax(0,1fr)]). */
|
||
/* --gtc-20 through --gtc-23 retired with the removed Cloud Upstream page. */
|
||
--gtc-24: minmax(0,1.5fr) 120px 120px 180px; /* Extracted from ui/src/pages/CompanyAccess.tsx (grid-cols-[minmax(0,1.5fr)_120px_120px_180px]). */
|
||
--gtc-25: 19rem minmax(0,1fr); /* Extracted from ui/src/pages/CompanyExport.tsx (grid-cols-[19rem_minmax(0,1fr)]). */
|
||
--gtc-26: 7rem minmax(0,1fr); /* Extracted from ui/src/pages/CompanySkills.tsx (grid-cols-[7rem_minmax(0,1fr)]). */
|
||
--gtc-27: minmax(0,1fr) auto auto; /* Extracted from ui/src/pages/CompanySkills.tsx (grid-cols-[minmax(0,1fr)_auto_auto]). */
|
||
--gtc-28: 13rem minmax(0,1fr); /* Extracted from ui/src/pages/CompanySkills.tsx (grid-cols-[13rem_minmax(0,1fr)]). */
|
||
--gtc-29: minmax(0,1fr) 18rem; /* Extracted from ui/src/pages/CompanySkills.tsx (grid-cols-[minmax(0,1fr)_18rem]). */
|
||
--gtc-30: 14rem minmax(0,1fr); /* Extracted from ui/src/pages/CompanySkills.tsx (grid-cols-[14rem_minmax(0,1fr)]). */
|
||
--gtc-31: 1.3fr,1fr; /* Extracted from ui/src/pages/Costs.tsx (grid-cols-[1.3fr,1fr]). */
|
||
--gtc-32: 1.25fr,0.95fr; /* Extracted from ui/src/pages/Costs.tsx (grid-cols-[1.25fr,0.95fr]). */
|
||
--gtc-33: 1.2fr,0.95fr; /* Extracted from ui/src/pages/Costs.tsx (grid-cols-[1.2fr,0.95fr]). */
|
||
--gtc-34: 320px minmax(0,1fr); /* Extracted from ui/src/pages/InstanceAccess.tsx (grid-cols-[320px_minmax(0,1fr)]). */
|
||
--gtc-35: minmax(10rem,14rem) 1fr; /* Extracted from ui/src/pages/InstanceExperimentalSettings.tsx (grid-cols-[minmax(10rem,14rem)_1fr]). */
|
||
--gtc-36: minmax(0,1.15fr) minmax(360px,0.85fr); /* Extracted from ui/src/pages/InviteLanding.tsx (grid-cols-[minmax(0,1.15fr)_minmax(360px,0.85fr)]). */
|
||
--gtc-37: minmax(0,1.15fr) minmax(320px,0.85fr); /* Extracted from ui/src/pages/InviteUxLab.tsx (grid-cols-[minmax(0,1.15fr)_minmax(320px,0.85fr)]). */
|
||
--gtc-38: minmax(0,0.92fr) minmax(0,1.08fr); /* Extracted from ui/src/pages/InviteUxLab.tsx (grid-cols-[minmax(0,0.92fr)_minmax(0,1.08fr)]). */
|
||
--gtc-39: minmax(0,1.2fr) 320px; /* Extracted from ui/src/pages/InviteUxLab.tsx (grid-cols-[minmax(0,1.2fr)_320px]). */
|
||
--gtc-40: minmax(0,1fr) 320px; /* Extracted from ui/src/pages/IssueChatLongThreadPerf.tsx (grid-cols-[minmax(0,1fr)_320px]). */
|
||
--gtc-41: 10rem minmax(0,1fr); /* Extracted from ui/src/pages/PipelineSettings.tsx (grid-cols-[10rem_minmax(0,1fr)]). */
|
||
--gtc-42: minmax(0,1fr) 240px; /* Extracted from ui/src/pages/PipelineSettings.tsx (grid-cols-[minmax(0,1fr)_240px]). */
|
||
--gtc-43: minmax(0,1fr) minmax(0,1fr); /* Extracted from ui/src/pages/PipelineSettings.tsx (grid-cols-[minmax(0,1fr)_minmax(0,1fr)]). */
|
||
--gtc-44: 5rem 240px; /* Extracted from ui/src/pages/PipelineSettings.tsx (grid-cols-[5rem_240px]). */
|
||
--gtc-45: minmax(0,1fr) 340px; /* Extracted from ui/src/pages/Pipelines.tsx (grid-cols-[minmax(0,1fr)_340px]). */
|
||
--gtc-46: 18px minmax(0,1fr); /* Extracted from ui/src/pages/Pipelines.tsx (grid-cols-[18px_minmax(0,1fr)]). */
|
||
--gtc-47: 120px 1fr; /* Extracted from ui/src/pages/Pipelines.tsx (grid-cols-[120px_1fr]). */
|
||
--gtc-48: 18px minmax(0,1fr) auto; /* Extracted from ui/src/pages/Pipelines.tsx (grid-cols-[18px_minmax(0,1fr)_auto]). */
|
||
--gtc-49: 18px 1fr auto; /* Extracted from ui/src/pages/Pipelines.tsx (grid-cols-[18px_1fr_auto]). */
|
||
--gtc-50: 1fr 280px; /* Extracted from ui/src/pages/Pipelines.tsx (grid-cols-[1fr_280px]). */
|
||
--gtc-51: 160px 1fr; /* Extracted from ui/src/pages/Pipelines.tsx (grid-cols-[160px_1fr]). */
|
||
--gtc-52: minmax(0,1.4fr) minmax(220px,0.8fr); /* Extracted from ui/src/pages/PluginSettings.tsx (grid-cols-[minmax(0,1.4fr)_minmax(220px,0.8fr)]). */
|
||
--gtc-53: minmax(0,1.4fr) minmax(18rem,0.9fr); /* Extracted from ui/src/pages/ProjectWorkspaceDetail.tsx (grid-cols-[minmax(0,1.4fr)_minmax(18rem,0.9fr)]). */
|
||
--gtc-54: minmax(12rem,2.4fr) minmax(5.25rem,0.75fr) minmax(7rem,1fr) minmax(5rem,0.7fr) 2.75rem; /* Extracted from ui/src/pages/Secrets.tsx (grid-cols-[minmax(12rem,2.4fr)_minmax(5.25rem,0.75fr)_minmax(7rem,1fr)_minmax(5rem,0.7fr)_2.75rem]). */
|
||
--gtc-55: 8rem minmax(0,1fr); /* Extracted from ui/src/pages/Secrets.tsx (grid-cols-[8rem_minmax(0,1fr)]). */
|
||
--gtc-56: minmax(0,1fr) minmax(14rem,18rem); /* Extracted from ui/src/pages/TeamCatalog.tsx (grid-cols-[minmax(0,1fr)_minmax(14rem,18rem)]). */
|
||
--gtc-57: repeat(14,minmax(0,1fr)); /* Extracted from ui/src/pages/UserProfile.tsx (grid-cols-[repeat(14,minmax(0,1fr))]). */
|
||
--gtc-58: auto 1fr auto; /* Extracted from ui/src/pages/UserProfile.tsx (grid-cols-[auto_1fr_auto]). */
|
||
--gtc-59: 8rem 1fr;
|
||
--gtc-60: 1fr 1fr;
|
||
--gtc-61: 1fr 1fr 1fr;
|
||
--gtc-62: 1fr minmax(0,18rem);
|
||
--gtc-63: 280px 1fr;
|
||
--gtc-64: minmax(0,20rem) 1fr;
|
||
--gtc-65: minmax(8rem,0.38fr) minmax(0,0.62fr) auto;
|
||
--gtr-1: auto auto; /* Extracted from ui/src/components/ui/card.tsx (grid-rows-[auto_auto]). */
|
||
--gtr-2: 1fr; /* Extracted from ui/src/pages/CompanySkills.tsx (grid-rows-[1fr]). */
|
||
--gtr-3: 0fr; /* Extracted from ui/src/pages/CompanySkills.tsx (grid-rows-[0fr]). */
|
||
--tp-background-color-border-color: background-color,border-color; /* Extracted from ui/src/components/ActivityFeed.tsx (transition-[background-color,border-color]). */
|
||
--tp-width-background-color: width,background-color; /* Extracted from ui/src/components/BudgetPolicyCard.tsx (transition-[width,background-color]). */
|
||
--tp-width: width; /* Extracted from ui/src/components/ClaudeSubscriptionPanel.tsx (transition-[width]). */
|
||
--tp-background-color-color-opacity: background-color,color,opacity; /* Extracted from ui/src/components/FileTree.tsx (transition-[background-color,color,opacity]). */
|
||
--tp-max-height: max-height; /* Extracted from ui/src/components/FoldCurtain.tsx (transition-[max-height]). */
|
||
--tp-border-color-background-color-box-shadow: border-color,background-color,box-shadow; /* Extracted from ui/src/components/IssueChatThread.tsx (transition-[border-color,background-color,box-shadow]). */
|
||
--tp-width-min-width: width,min-width; /* Extracted from ui/src/components/KanbanBoard.tsx (transition-[width,min-width]). */
|
||
--tp-width-opacity: width,opacity; /* Extracted from ui/src/components/OnboardingWizard.tsx (transition-[width,opacity]). */
|
||
--tp-background-color-right: background-color,right; /* Extracted from ui/src/components/ScrollToBottom.tsx (transition-[background-color,right]). */
|
||
--tp-background-color-border-color-color: background-color,border-color,color; /* Extracted from ui/src/components/SystemNotice.tsx (transition-[background-color,border-color,color]). */
|
||
--tp-transform-opacity: transform,opacity; /* Extracted from ui/src/components/ToastViewport.tsx (transition-[transform,opacity]). */
|
||
--tp-color-box-shadow: color,box-shadow; /* Extracted from ui/src/components/ui/badge.tsx (transition-[color,box-shadow]). */
|
||
--tp-color-background-color-border-color-box-shadow-opacity: color,background-color,border-color,box-shadow,opacity; /* Extracted from ui/src/components/ui/button.tsx (transition-[color,background-color,border-color,box-shadow,opacity]). */
|
||
--tp-border-color-background-color: border-color,background-color; /* Extracted from ui/src/components/ui/radio-card.tsx (transition-[border-color,background-color]). */
|
||
--tp-color-background-color-border-color-box-shadow: color,background-color,border-color,box-shadow; /* Extracted from ui/src/components/ui/tabs.tsx (transition-[color,background-color,border-color,box-shadow]). */
|
||
--tp-grid-template-rows-opacity: grid-template-rows,opacity; /* Extracted from ui/src/pages/CompanySkills.tsx (transition-[grid-template-rows,opacity]). */
|
||
--tp-box-shadow-border-color: box-shadow,border-color; /* Extracted from ui/src/pages/OrgChart.tsx (transition-[box-shadow,border-color]). */
|
||
--tp-box-shadow: box-shadow; /* Extracted from ui/src/pages/ProjectDetail.tsx (transition-[box-shadow]). */
|
||
--tp-transform-box-shadow: transform,box-shadow; /* Extracted from ui/src/pages/ProjectDetail.tsx (transition-[transform,box-shadow]). */
|
||
--z-2: 2; /* Extracted from ui/src/components/DocumentAnnotationLayer.tsx (z-[2]). */
|
||
--z-60: 60; /* Extracted from ui/src/components/DocumentAnnotationPanel.tsx (z-[60]). */
|
||
--z-1: 1; /* Extracted from ui/src/components/IssueDocumentAnnotations.tsx (z-[1]). */
|
||
--z-200: 200; /* Extracted from ui/src/components/Layout.tsx (z-[200]). */
|
||
--z-9999: 9999; /* Extracted from ui/src/components/MarkdownEditor.tsx (z-[9999]). */
|
||
--z-120: 120; /* Extracted from ui/src/components/ToastViewport.tsx (z-[120]). */
|
||
--s-0_98: 0.98; /* Extracted from ui/src/pages/Inbox.tsx (scale-[0.98]). */
|
||
--s-1_02: 1.02; /* Extracted from ui/src/components/CaseAttachmentsGallery.tsx (scale-[1.02]). */
|
||
--e-cubic-bezier-0_16-1-0_3-1: cubic-bezier(0.16,1,0.3,1); /* Extracted from ui/src/components/ui/alert-dialog.tsx (ease-[cubic-bezier(0.16,1,0.3,1)]). */
|
||
--va-0_125em: -0.125em; /* Extracted from ui/src/components/ExternalObjectStatusIcon.tsx (align-[-0.125em]). */
|
||
--sw-2_3: 2.3; /* Extracted from ui/src/components/MobileBottomNav.tsx (stroke-[2.3]). */
|
||
--blur-1px: 1px; /* Extracted from ui/src/components/ChatComposer.tsx (backdrop-blur-[1px]). */
|
||
--blur-2px: 2px; /* Extracted from ui/src/components/IssueChatThread.tsx (backdrop-blur-[2px]). */
|
||
--drop-shadow-extract-1: 0 1px 2px rgba(0,0,0,0.65); /* Extracted from ui/src/components/CompanyPatternIcon.tsx (drop-shadow-[0_1px_2px_rgba(0,0,0,0.65)]). */
|
||
--code-bg-resolved: var(--paperclip-code-bg, var(--muted)); /* Extracted from ui/src/components/FileViewerSheet.tsx (bg-[var(--paperclip-code-bg,theme(colors.muted.DEFAULT))]). */
|
||
--paperclip-code-highlight-bg: rgba(250,204,21,0.12); /* DECISION-SHEET.md A5: FileViewerSheet.tsx referenced this var without it ever being defined, relying on the var() fallback; minted here at exactly the former fallback value (zero pixels). */
|
||
--paperclip-code-highlight-border: rgb(234,179,8); /* DECISION-SHEET.md A5: same as --paperclip-code-highlight-bg — minted at the former fallback value. */
|
||
--code-highlight-bg-resolved: var(--paperclip-code-highlight-bg); /* Extracted from ui/src/components/FileViewerSheet.tsx; fallback dropped in A5 now that --paperclip-code-highlight-bg is a real token (was var(--paperclip-code-highlight-bg, rgba(250,204,21,0.12))). */
|
||
--code-gutter-fg-resolved: var(--paperclip-code-gutter-fg, var(--muted-foreground)); /* Extracted from ui/src/components/FileViewerSheet.tsx (text-[var(--paperclip-code-gutter-fg,theme(colors.muted.foreground))]). */
|
||
--code-highlight-border-resolved: var(--paperclip-code-highlight-border); /* Extracted from ui/src/components/FileViewerSheet.tsx; fallback dropped in A5 now that --paperclip-code-highlight-border is a real token (was var(--paperclip-code-highlight-border, rgb(234,179,8))). */
|
||
--gradient-extract-25: linear-gradient(135deg,hsl(var(--primary)) 0%,hsl(var(--accent)) 55%,hsl(var(--muted)) 100%); /* Extracted from ui/src/components/SidebarAccountMenu.tsx (bg-[linear-gradient(135deg,hsl(var(--primary))_0%,hsl(var(--accent))_55%,hsl(var(--muted))_100%)]). */
|
||
--gradient-extract-26: linear-gradient(135deg,hsl(var(--primary)) 0%,hsl(var(--accent)) 58%,color-mix(in oklab,hsl(var(--background)) 76%,white 24%) 100%); /* Extracted from ui/src/pages/ProfileSettings.tsx (bg-[linear-gradient(135deg,hsl(var(--primary))_0%,hsl(var(--accent))_58%,color-mix(in_oklab,hsl(var(--background))_76%,white_24%)_100%)]). */
|
||
}
|
||
|
||
/* ── ALLOWLIST (Phase 2, design/token-extraction) ──────────────────────────
|
||
Canonical, MACHINE-READABLE allowlist consumed by
|
||
scripts/check-token-gates.mjs. This block consolidates every site the
|
||
Batch 1-4 codemods intentionally left un-tokenized (functional/third-party
|
||
values, CSS keywords, and currently-inert dead classes) into one list the
|
||
gate script parses directly, rather than re-deriving it from each batch's
|
||
prose log above. The per-batch prose blocks above are kept as historical
|
||
narrative (why each decision was made); this block is the source of truth
|
||
for WHICH sites are exempt.
|
||
|
||
CRITERIA UPDATE (Run 2 review, DECISION-SHEET.md B1 user ruling): the
|
||
allowlist criteria now also include FIRST-PARTY INTENTIONAL ONE-OFF
|
||
DECORATION — deliberate, never-reused decorative gradients/shadows on
|
||
demo/UX-lab surfaces stay inline (spirit over letter of DESIGN.md
|
||
principle 2) rather than being minted as singleton --gradient-extract-*
|
||
and --shadow-extract-* tokens. Production-surface decoration tokens were
|
||
KEPT; only tokens whose sole consumers were *UxLab.tsx demo pages were
|
||
reverted to their original literal form under this rule.
|
||
|
||
Format (one line per entry, do not reflow):
|
||
* allow <repo-relative-path> — <reason>
|
||
|
||
A path may appear more than once if it has more than one distinct
|
||
allowlisted concern. Paths are matched by the gate script as a substring
|
||
of the file being checked (repo-relative, POSIX-style).
|
||
* allow ui/src/pages/CompanyEnvironments.tsx — xterm.js terminal theme config (background/foreground/cursor/cursorAccent/selectionBackground hex, fontSize: 12); functional third-party option object, not a rendered CSS value
|
||
* allow ui/src/pages/CompanySettings.tsx — <input type="color"> value; the DOM color-picker control requires a real hex string
|
||
* allow ui/src/components/issue-properties/IssueProperties.tsx — newLabelColor color-picker seed state persisted into the label-create payload sent to the backend; a var() string would corrupt that payload
|
||
* allow ui/src/pages/CompanySkills.tsx — DISCOVERY_ACCENTS palette array feeds skill.color, persisted/compared JS data (SkillCreateDraft), not just a rendered value; also fontSize: Math.round(size * 0.42), computed at runtime from a prop, not a static literal
|
||
* allow ui/src/components/IssueColumns.tsx — accentColor fallback also feeds pickTextColorForPillBg() contrast math (lib/color-contrast.ts), which needs a real hex string to compute luminance
|
||
* allow ui/src/components/CompanyPatternIcon.tsx — canvas 2D fillStyle built from a runtime-computed template literal, not a static literal at all
|
||
* allow ui/src/pages/InviteUxLab.tsx — brandColor prop (x2) feeds CompanyPatternIcon's hexToHue() color math via the same canvas-fill code path; demo/showcase-only prop, not a rendered CSS value
|
||
* allow ui/src/components/ui/scroll-area.tsx — rounded-[inherit] is a CSS keyword, not a literal value; nothing to extract
|
||
* allow ui/src/components/ui/dialog.tsx — tw-animate-css plugin utilities (zoom-in-[0.97], zoom-out-[0.97], slide-in-from-top-[1%], slide-out-to-top-[1%], animate-in, animate-out, fade-in-0, fade-out-0) are dead/no-op classes today (the tw-animate-css plugin is not installed and no matching @utility exists in this file; confirmed via grep of the built storybook-static CSS — none of these class names emit any rule); nothing to tokenize without visually changing a currently-inert class
|
||
* allow ui/src/components/ui/alert-dialog.tsx — same tw-animate-css dead-class situation as dialog.tsx (zoom-out-[0.97], zoom-in-[0.97])
|
||
* allow ui/src/components/ProjectWorkspaceSummaryCard.tsx — min-[420px]: is a Tailwind arbitrary BREAKPOINT variant (responsive container-query-style prefix), not a value applied to a property; the variant position cannot reference a CSS custom property, so it cannot be tokenized without changing Tailwind's own variant syntax
|
||
* allow ui/src/components/FileTree.tsx — max-[480px]: arbitrary breakpoint variant (x2), same reasoning as min-[420px] above
|
||
* allow ui/src/pages/RunTranscriptUxLab.tsx — first-party intentional one-off decoration (demo/UX-lab page): hero/card gradients + shadows reverted from singleton tokens per DECISION-SHEET.md B1 user ruling
|
||
* allow ui/src/pages/InviteUxLab.tsx — first-party intentional one-off decoration (demo/UX-lab page): hero/card gradients + shadows reverted from singleton tokens per DECISION-SHEET.md B1 user ruling
|
||
* allow ui/src/pages/IssueChatUxLab.tsx — first-party intentional one-off decoration (demo/UX-lab page): hero/card gradients + shadows reverted from singleton tokens per DECISION-SHEET.md B1 user ruling
|
||
* allow ui/src/components/onboarding/OnboardingAuthScreens.tsx — Google/GitHub official brand-mark SVG fills (third-party logo colors); brand identity values, not themeable roles
|
||
* allow ui/src/components/onboarding/onboarding-data.ts — connector glyph colors are third-party brand hexes (GitHub/Google/Notion/Linear/…) rendered as logo-tile backgrounds; brand identity values, not themeable roles
|
||
* allow ui/src/pages/SystemNoticeUxLab.tsx — first-party intentional one-off decoration (demo/UX-lab page): hero/card gradients + shadows reverted from singleton tokens per DECISION-SHEET.md B1 user ruling
|
||
|
||
Test-fixture hex-literal policy (Batch 4, resolving TOKEN-AUDIT.md section
|
||
8 item 10 "Needs human decision" for these specific sites): the following
|
||
*.test.tsx files pass hex-literal strings as MOCK PROP / MOCK API-RESPONSE
|
||
DATA (label/project/company/brand `color` fields, xterm.js theme-mock
|
||
assertions) — not as Tailwind class strings or CSS-in-JS style
|
||
declarations. These are the test-file counterpart of the same
|
||
"functional/persisted data, not a rendered CSS value" category Batch 1
|
||
already allowlisted in production source (see CompanySkills.tsx,
|
||
IssueProperties.tsx, IssueColumns.tsx above); tokenizing them would
|
||
replace realistic-looking mock data with `var()` strings that no longer
|
||
resemble what a real API response contains, without touching any
|
||
rendered pixel. Each file is listed individually for reviewability.
|
||
* allow ui/src/components/IssueFiltersPopover.test.tsx — mock label color prop
|
||
* allow ui/src/components/IssueProperties.test.tsx — mock label/project color fixtures
|
||
* allow ui/src/components/MarkdownBody.test.tsx — mock project color fed through a mention-href builder
|
||
* allow ui/src/components/MarkdownEditor.test.tsx — mock projectColor mention fixture
|
||
* allow ui/src/components/NewIssueDialog.test.tsx — mock brandColor/label color fixtures
|
||
* allow ui/src/components/ProjectTile.test.tsx — mock project color prop
|
||
* allow ui/src/components/RoutineRunVariablesDialog.test.tsx — mock label color fixture
|
||
* allow ui/src/components/SidebarCompanyMenu.test.tsx — mock brandColor company fixtures
|
||
* allow ui/src/components/SidebarProjects.test.tsx — mock project color fixture
|
||
* allow ui/src/components/SidebarStarredProjects.test.tsx — mock project color fixture
|
||
* allow ui/src/pages/CompanyEnvironments.test.tsx — xterm.js theme-mock assertion values (mirrors the allowlisted xterm.js config in the CompanyEnvironments.tsx source above)
|
||
* allow ui/src/pages/ExecutionWorkspaceDetail.test.tsx — mock project color fixture
|
||
* allow ui/src/pages/InviteLanding.test.tsx — mock companyBrandColor fixtures
|
||
* allow ui/src/pages/IssueDetail.test.tsx — mock project color fixture
|
||
* allow ui/src/pages/ProjectDetail.test.tsx — mock project color fixture
|
||
* allow ui/src/pages/ProjectWorkspaceDetail.test.tsx — mock project color fixture
|
||
* allow ui/src/pages/Projects.test.tsx — mock project color fixture
|
||
* allow ui/src/pages/Routines.test.tsx — mock routine/label color fixtures
|
||
── end allowlist ── */
|