Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions .fork/customizations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1632,9 +1632,14 @@
live in theme.custom.palettes.css (two-block stage + v2 panel). index.html
pre-paints #141618.

The sidebar's project chrome — the Projects row, each project header and
each thread card's meta line — rests on white at 65% rather than the
panel's opaque #8a8a8a muted ink.
The sidebar's project chrome — the Projects row, the recent-projects
strip's + marks, each project and status section header, and each
thread card's meta line and hover actions — rests on white at 65% rather
than the panel's opaque #8a8a8a muted ink (a status section's count stays
a step fainter at muted/60). The footer's icons (Settings, Pull Requests,
the update pill at rest), the draft pencil, the idle circle, receded card
titles and Gray project icons (the server's "No project" default) take
the same white.

The thread you are on is a pane of glass (Figma 532:6527): a hairline
glint on the top and bottom runs, a dark 1px halo, soft inner sheens, and
Expand Down Expand Up @@ -1678,6 +1683,8 @@
- apps/web/index.html
# Fenced data-fork-glass-rim on the details card.
- apps/web/src/components/chat/ThreadDetailsCard.tsx
# Fenced data-fork-thread-card-actions on the row's hover actions cell.
- apps/web/src/components/Sidebar.tsx
# Imports the glass surfaces sheet after the palettes sheet (fenced).
- apps/web/src/main.tsx
# Stamps data-chat-composer-floating-layer on every composer popup, which
Expand Down
63 changes: 47 additions & 16 deletions apps/web/src/__fork_guards__/forkCoolDarkerTheme.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,26 +145,57 @@ describe("fork guard: fork-cool-darker-theme", () => {
);
});

it("inks the project chrome and card meta as white at 65%, on the derived token", () => {
const projectChrome = ruleBodyFor(themeRules, [
...DARKER_PANEL,
":is([data-fork-projects-row], [data-fork-project-section-header], [data-fork-thread-card-meta])",
]);
it("states the panel's quiet ink once, as white at 65%", () => {
const panel = ruleBodyFor(themeRules, DARKER_PANEL);
expect(panel).toMatch(/\s--fork-glass-quiet-ink:\s*rgb\(255 255 255 \/ 65%\);/u);
// The panel's muted channel itself stays opaque for everything else.
expect(declarationHex(panel, "--muted-foreground")).toBe("#8a8a8a");
});

it("inks the opted-in sidebar chrome with the quiet ink", () => {
const quietInk = /:\s*var\(--fork-glass-quiet-ink\);/u;
const panel = ruleBodyFor(themeRules, DARKER_PANEL);
expect(panel).toMatch(new RegExp(`--fork-sidebar-card-title-receded${quietInk.source}`, "u"));
expect(panel).toMatch(new RegExp(`--color-gray-400${quietInk.source}`, "u"));
// The derived token: text-muted-foreground reads it, and restating
// --muted-foreground below [data-app-sidebar] would not re-derive it.
expect(projectChrome).toMatch(/\s--contrast-muted-foreground:\s*rgb\(255 255 255 \/ 65%\);/u);
// The panel's muted channel itself stays opaque for everything else.
expect(declarationHex(ruleBodyFor(themeRules, DARKER_PANEL), "--muted-foreground")).toBe(
"#8a8a8a",
expect(
ruleBodyFor(themeRules, [
...DARKER_PANEL,
":is( [data-fork-projects-row], [data-fork-recent-projects], [data-fork-project-section-header], [data-fork-status-section-header], [data-fork-thread-card-meta], [data-fork-thread-card-actions], [data-fork-idle-mark] )",
]),
).toMatch(new RegExp(`--contrast-muted-foreground${quietInk.source}`, "u"));
// The draft pencil is drawn at muted/70, so it takes the ink directly.
expect(ruleBodyFor(themeRules, [...DARKER_PANEL, "[data-fork-draft-pencil]"])).toMatch(
new RegExp(`\\scolor${quietInk.source}`, "u"),
);
// Every hook has to exist for the rule to land on anything.
expect(readSibling("../custom/SidebarV2ChromeRows.tsx")).toContain('data-fork-projects-row=""');
expect(readSibling("../custom/SidebarV2ProjectGroupHeader.tsx")).toContain(
'data-fork-project-section-header=""',
);
expect(readSibling("../custom/SidebarV2ThreadCardMeta.tsx")).toContain(
'data-fork-thread-card-meta=""',
// The footer icons ink from --sidebar-icon-color, so they take it there.
expect(ruleBodyFor(themeRules, [...DARKER_PANEL, '[data-slot="sidebar-footer"]'])).toMatch(
new RegExp(`--sidebar-icon-color${quietInk.source}`, "u"),
);
// Every hook has to exist for the rule to land on anything.
for (const [file, hook] of [
["../custom/SidebarV2ChromeRows.tsx", "data-fork-projects-row"],
["../custom/SidebarV2RecentProjectsStrip.tsx", "data-fork-recent-projects"],
["../custom/SidebarV2ProjectGroupHeader.tsx", "data-fork-project-section-header"],
["../custom/SidebarV2StatusSectionHeader.tsx", "data-fork-status-section-header"],
["../custom/SidebarV2ThreadCardMeta.tsx", "data-fork-thread-card-meta"],
["../components/Sidebar.tsx", "data-fork-thread-card-actions"],
["../custom/SidebarV2StatusIndicator.tsx", "data-fork-idle-mark"],
["../custom/SidebarV2StatusIndicator.tsx", "data-fork-draft-pencil"],
] as const) {
expect(readSibling(file), `${hook} in ${file}`).toContain(`${hook}=""`);
}
});

it("keeps gray-400 to the Gray project colour, since Glass repaints it", () => {
// Glass redefines --color-gray-400 for the whole v2 sidebar, which is only
// safe while nothing else in the web app draws with it.
const srcRoot = NodeURL.fileURLToPath(new URL("..", import.meta.url));
const users = (NodeFS.readdirSync(srcRoot, { recursive: true }) as string[])
.filter((file) => /\.tsx?$/u.test(file) && !/\.test\.tsx?$/u.test(file))
.filter((file) => NodeFS.readFileSync(`${srcRoot}/${file}`, "utf8").includes("gray-400"));
expect(users).toEqual(["projectIconColors.ts"]);
});

it("pre-paints Glass from the palette key so the load flash matches the stage", () => {
Expand Down
3 changes: 3 additions & 0 deletions apps/web/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1989,6 +1989,9 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
{hasHoverActions ? (
/* fork:end sidebar-v2-draft-rows */
<span
/* fork:begin fork-cool-darker-theme — see .fork/customizations.yaml#fork-cool-darker-theme */
data-fork-thread-card-actions=""
/* fork:end fork-cool-darker-theme */
className={cn(
// Zero-width at rest so a settled row's title still runs
// the full width of the card when nothing is pointing at
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/custom/SidebarV2StatusIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ function SidebarV2StatusDot({ tone }: { tone: SidebarV2DotTone }) {
mark in every state instead of switching between a mark and a string. */
export function SidebarV2IdleMark() {
return (
<span aria-hidden className={MARK_SLOT_CLASS}>
<span aria-hidden data-fork-idle-mark="" className={MARK_SLOT_CLASS}>
<span className="size-2.5 rounded-full border border-muted-foreground" />
</span>
);
Expand All @@ -406,7 +406,7 @@ function SidebarV2WokeMark() {
export function SidebarV2DraftMark() {
return (
<span aria-hidden className={MARK_SLOT_CLASS}>
<PenLineIcon className="size-2.5 text-muted-foreground/70" />
<PenLineIcon data-fork-draft-pencil="" className="size-2.5 text-muted-foreground/70" />
</span>
);
}
Expand Down
57 changes: 47 additions & 10 deletions apps/web/src/theme.custom.palettes.css
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,14 @@
--secondary-label: var(--muted-foreground);
--icon-muted: var(--muted-foreground);
--sidebar-muted-foreground: #d0d4d6;
--fork-sidebar-card-title-receded: #aeb2b6;
/* The panel's quiet ink where it should tint with the glass under it. The
opaque #8a8a8a above stays the default; named elements opt in below. */
--fork-glass-quiet-ink: rgb(255 255 255 / 65%);
--fork-sidebar-card-title-receded: var(--fork-glass-quiet-ink);
/* Gray project icons (the server's "No project" default) would otherwise
be the one flat grey left among them. gray-400 is only that colour's
dark ink in apps/web; the Glass guard holds that. */
--color-gray-400: var(--fork-glass-quiet-ink);
--sidebar-control-surface: #2f3437;
--sidebar-row-hover: #282d30;
--sidebar-row-active: #2c3134;
Expand All @@ -258,20 +265,47 @@
border-radius: 9999px;
}

/* Project chrome — the Projects row, every project header and each thread
card's meta line (project chip, branch, model) — rests on white at 65%
instead of the panel's #8a8a8a, so the labels, marks and icon buttons tint
with the surface under them. The panel's opaque ink above stays for
everything else that reads the muted channel.
/* Project chrome — the Projects row, the recent-projects strip, every project
and status section header, each thread card's meta line (project chip,
branch, model), its hover actions (pin, snooze, settle) and the idle
circle — rests on the quiet ink instead of the panel's #8a8a8a, so the
labels, marks, carets and icon buttons tint with the surface under them.
This is an allowlist: anything not named here or below keeps the opaque
ink.

The derived token, not --muted-foreground: index.css computes
--contrast-muted-foreground on [data-app-sidebar] and a descendant inherits
the result, so restating the source here would change nothing. Stated flat,
so the appearance-contrast slider does not move these labels. */
:root[data-fork="noahhendrickson-t3code"].dark[data-fork-theme="cool-darker"]
[data-sidebar-version="v2"]
:is([data-fork-projects-row], [data-fork-project-section-header], [data-fork-thread-card-meta]) {
--contrast-muted-foreground: rgb(255 255 255 / 65%);
:is(
[data-fork-projects-row],
[data-fork-recent-projects],
[data-fork-project-section-header],
[data-fork-status-section-header],

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Status section count lands at ~39%, not 65%. SidebarV2StatusSectionHeader.tsx:63 draws the count with text-muted-foreground/60. With the token now rgb(255 255 255 / 65%), Tailwind's color-mix(… 60%, transparent) multiplies that to about 39% white. That's the same multiply the draft pencil gets its own rule to avoid. The PR body lists "Status section headers (label, count, caret)" as white at 65%.

If the count is meant to stay a step quieter than the label, this is fine, but then the description and manifest overstate it. If it should match, it needs the same direct-color treatment as [data-fork-draft-mark] svg.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Intended: the count stays a step fainter than its label, as it was in every other theme. I corrected the PR body and the manifest note so they no longer claim the count is at 65% (9151acb).

[data-fork-thread-card-meta],
[data-fork-thread-card-actions],
[data-fork-idle-mark]
) {
--contrast-muted-foreground: var(--fork-glass-quiet-ink);
}

/* The draft pencil too. It is drawn at muted/70, which would multiply the
token above down to 45%, so it takes the ink directly. */
:root[data-fork="noahhendrickson-t3code"].dark[data-fork-theme="cool-darker"]
[data-sidebar-version="v2"]
[data-fork-draft-pencil] {
color: var(--fork-glass-quiet-ink);
}

/* The footer's icons (Settings, Pull Requests, the update pill at rest) ink
from --sidebar-icon-color, not the muted channel, which the vibrancy block
freezes to opaque #8a8a8a for the rest of the panel. */
:root[data-fork="noahhendrickson-t3code"].dark[data-fork-theme="cool-darker"]
[data-sidebar-version="v2"]
[data-slot="sidebar-footer"] {
--sidebar-icon-color: var(--fork-glass-quiet-ink);
}

/* ---------------------------------------------------------------------------
Expand Down Expand Up @@ -1180,8 +1214,11 @@
60% plus 40% of the panel's alpha, and the rest is wallpaper showing
through the icon itself, which reads as the icons taking on the desktop's
colour. Frozen to the value that mix resolves to against the opaque panel (#d0d4d6 60% over
#181b1e) so icons stay glass-independent. Anything else derived from the
panel fill needs the same treatment. */
#181b1e). The freeze guards against that accidental leak of the panel's
alpha, so anything else derived from the panel fill needs the same
treatment. The deliberate flat white wash some elements take instead
(--fork-glass-quiet-ink, the footer included) is a separate, intended
choice, not a regression of this one. */
--sidebar-icon-color: #8a8a8a;
/* Upstream's appearance-contrast control (#7906) derives these two from
--sidebar the same way, so they inherit the panel's alpha and every
Expand Down
Loading