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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
- Update `data/coding-agents.json` only through `bun run data:refresh`; preserve duplicate, retention, stable-key, metric-coverage, material-change, and bounded-history guards.
- Regenerate `lib/chart-colors.generated.ts` through the checked iWantHue script. Do not hand-edit generated files.
- Keep pointer, keyboard, focus, and touch behavior equivalent. Preserve semantic landmarks, accessible names, visible focus, and responsive horizontal chart panning.
- Give every ordinary themed page exactly one shared icon-menu appearance control as the final action in its header. Do not put appearance controls in footers, content, or fallback action rows.
- Keep analytics cookieless and production-only. Add only allowlisted events with controlled properties; do not send URLs, query strings, free-form text, identities, or persistent identifiers.
- Never expose `POSTHOG_API_KEY`, commit credentials, or provider secrets in browser variables, source, logs, fixtures, or documentation.
- Run narrow tests while iterating and `bun run check` before handoff.
6 changes: 6 additions & 0 deletions app/blog/blog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ describe("AI Charts benchmark notes", () => {
expect(markup).toContain('class="plain-nav"');
expect(markup).toContain('href="/blog"');
expect(markup).toContain('href="/"');
expect(markup.match(/data-presentation="menu"/gu)).toHaveLength(1);
expect(markup.indexOf('>Chart</a>')).toBeLessThan(
markup.indexOf('data-presentation="menu"'),
);
expect(markup).toContain('class="plain-footer"');
expect(markup.slice(markup.indexOf('<footer class="plain-footer"')))
.not.toContain("hraness-design-theme-toggle");
expect(markup).toContain('aria-label="hraness"');
});

Expand Down
8 changes: 2 additions & 6 deletions app/blog/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HranessBrand, SkipLink, ThemeToggle } from "@/components/ui";
import { HranessBrand, SkipLink, ThemeMenuButton } from "@/components/ui";
import Link from "next/link";
import type { ReactNode } from "react";

Expand All @@ -17,11 +17,7 @@ export default function BlogLayout({
<Link href="/blog">Blog</Link>
<Link href="/data">Data</Link>
<Link href="/">Chart</Link>
<ThemeToggle
aria-label="Blog appearance"
presentation="menu"
size="compact"
/>
<ThemeMenuButton aria-label="Blog appearance" />
</nav>
</div>
</header>
Expand Down
6 changes: 6 additions & 0 deletions app/data/data.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,13 @@ describe("coding-agent dataset surface", () => {
expect(markup).toContain('aria-current="page" href="/data"');
expect(markup).toContain('href="/blog"');
expect(markup).toContain('href="/"');
expect(markup.match(/data-presentation="menu"/gu)).toHaveLength(1);
expect(markup.indexOf('>Chart</a>')).toBeLessThan(
markup.indexOf('data-presentation="menu"'),
);
expect(markup).toContain('class="plain-footer"');
expect(markup.slice(markup.indexOf('<footer class="plain-footer"')))
.not.toContain("hraness-design-theme-toggle");
});

test("renders literal provenance, definitions, leaders, method, and limits", () => {
Expand Down
8 changes: 2 additions & 6 deletions app/data/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HranessBrand, SkipLink, ThemeToggle } from "@/components/ui";
import { HranessBrand, SkipLink, ThemeMenuButton } from "@/components/ui";
import Link from "next/link";
import type { ReactNode } from "react";

Expand All @@ -17,11 +17,7 @@ export default function DataLayout({
<Link aria-current="page" href="/data">Data</Link>
<Link href="/blog">Blog</Link>
<Link href="/">Chart</Link>
<ThemeToggle
aria-label="Dataset appearance"
presentation="menu"
size="compact"
/>
<ThemeMenuButton aria-label="Dataset appearance" />
</nav>
</div>
</header>
Expand Down
16 changes: 14 additions & 2 deletions app/theme.test.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { expect, test } from "bun:test";
import {
DesignThemeProvider,
ThemeToggle,
ThemeMenuButton,
} from "@hraness/design-kit/react";
import { renderToStaticMarkup } from "react-dom/server";

import GlobalError from "./global-error";
import NotFound from "./not-found";

test("appearance starts with System and uses the shared persisted runtime", () => {
const html = renderToStaticMarkup(
<DesignThemeProvider storageKey="aicharts-theme">
<ThemeToggle aria-label="Chart appearance" presentation="menu" />
<ThemeMenuButton aria-label="Chart appearance" />
</DesignThemeProvider>,
);

Expand All @@ -29,3 +32,12 @@ test("AI Charts does not keep a second theme runtime", async () => {
expect(controls).not.toContain("localStorage");
expect(controls).not.toContain("matchMedia");
});

test("fallback documents remain control-free", () => {
const globalError = renderToStaticMarkup(
<GlobalError error={new Error("render failed")} reset={() => undefined} />,
);
const notFound = renderToStaticMarkup(<NotFound />);

expect(`${globalError}${notFound}`).not.toContain("hraness-design-theme-toggle");
});
4 changes: 2 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions components/coding-agent-explorer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ test("tucks data provenance behind a compact named control", async () => {
const source = await Bun.file(new URL("./coding-agent-explorer.tsx", import.meta.url)).text();
const topBarIndex = source.indexOf("<TopBar");
const provenanceIndex = source.indexOf('className="chart-provenance-control chart-selection-boundary"');
const themeIndex = source.indexOf('<ThemeMenuButton aria-label="Chart appearance" />');
const topBarClassIndex = source.indexOf('className="chart-top-bar"', topBarIndex);
const pageCanvasIndex = source.indexOf("<PageCanvas");

expect(source).toContain('tooltip="Data provenance"');
Expand All @@ -83,6 +85,11 @@ test("tucks data provenance behind a compact named control", async () => {
expect(source).toContain('placement="bottom end"');
expect(source).toContain('popoverClassName="share-menu-popover provenance-menu-popover chart-selection-boundary"');
expect(provenanceIndex).toBeGreaterThan(topBarIndex);
expect(themeIndex).toBeGreaterThan(provenanceIndex);
expect(themeIndex).toBeLessThan(topBarClassIndex);
expect(source.slice(themeIndex, topBarClassIndex)).toMatch(
/<ThemeMenuButton aria-label="Chart appearance" \/>\s*<\/>\s*\)\}/u,
);
expect(provenanceIndex).toBeLessThan(pageCanvasIndex);
expect(source).not.toContain('className="chart-subtitle-row"');
expect(source).not.toContain('className="chart-data-status"');
Expand Down
4 changes: 2 additions & 2 deletions components/coding-agent-explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
PageCanvas,
SegmentedControl,
TextField,
ThemeToggle,
ThemeMenuButton,
ToggleGroup,
TopBar,
type SegmentedItem,
Expand Down Expand Up @@ -883,7 +883,7 @@ export function CodingAgentExplorer({ brand, snapshot }: { brand: ChartBrand; sn
<LinkButton href="/blog" size="compact" variant="quiet">
Blog
</LinkButton>
<ThemeToggle aria-label="Chart appearance" />
<ThemeMenuButton aria-label="Chart appearance" />
</>
)}
className="chart-top-bar"
Expand Down
10 changes: 3 additions & 7 deletions components/ui.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { ThemeToggle as DesignThemeToggle } from "@hraness/design-kit/react";
import { ThemeMenuButton as DesignThemeMenuButton } from "@hraness/design-kit/react";
import { HugeiconsIcon } from "@hugeicons/react";
import {
Children,
Expand Down Expand Up @@ -363,16 +363,12 @@ export function ToggleGroup<Value extends string>({
);
}

export function ThemeToggle({
export function ThemeMenuButton({
"aria-label": ariaLabel,
presentation = "menu",
size = "compact",
}: Readonly<{
"aria-label": string;
presentation?: "menu";
size?: "compact";
}>) {
return <DesignThemeToggle aria-label={ariaLabel} presentation={presentation} size={size} />;
return <DesignThemeMenuButton aria-label={ariaLabel} />;
}

export function TextField({
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"kb:catalog": "bunx --bun github:hraness/kb#v0.15.2 catalog --root kb"
},
"dependencies": {
"@hraness/design-kit": "github:hraness/design-kit#v0.1.7",
"@hraness/design-kit": "github:hraness/design-kit#v0.1.8",
"@hraness/ui": "github:hraness/ui#v0.4.6",
"@hugeicons/core-free-icons": "^4.2.2",
"@hugeicons/react": "^1.1.9",
Expand Down