@estiva-app/ui 0.40.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/Chip.d.ts +10 -2
  2. package/dist/Chip.d.ts.map +1 -1
  3. package/dist/CommandPalette.d.ts.map +1 -1
  4. package/dist/Field.d.ts.map +1 -1
  5. package/dist/Form.d.ts +18 -1
  6. package/dist/Form.d.ts.map +1 -1
  7. package/dist/Link.d.ts +15 -1
  8. package/dist/Link.d.ts.map +1 -1
  9. package/dist/Select.d.ts.map +1 -1
  10. package/dist/eslint/index.js +16 -2
  11. package/dist/eslint/index.js.map +3 -3
  12. package/dist/eslint/no-restyled-part.d.ts +4 -0
  13. package/dist/eslint/no-restyled-part.d.ts.map +1 -1
  14. package/dist/gates/app-checks.d.ts.map +1 -1
  15. package/dist/gates/index.js +14 -0
  16. package/dist/gates/index.js.map +2 -2
  17. package/dist/index.js +53 -40
  18. package/dist/index.js.map +3 -3
  19. package/dist/looks.d.ts +6 -4
  20. package/dist/looks.d.ts.map +1 -1
  21. package/package.json +1 -1
  22. package/registry.json +54 -18
  23. package/src/Chip.mdx +13 -2
  24. package/src/Chip.stories.tsx +2 -0
  25. package/src/Chip.test.tsx +36 -0
  26. package/src/Chip.tsx +17 -5
  27. package/src/ChipInput.tsx +1 -1
  28. package/src/CommandPalette.tsx +6 -4
  29. package/src/DialogShell.stories.tsx +4 -3
  30. package/src/Field.mdx +5 -0
  31. package/src/Field.tsx +4 -1
  32. package/src/Form.mdx +9 -3
  33. package/src/Form.stories.tsx +18 -1
  34. package/src/Form.test.tsx +17 -3
  35. package/src/Form.tsx +20 -2
  36. package/src/Link.mdx +32 -0
  37. package/src/Link.stories.tsx +49 -0
  38. package/src/Link.test.tsx +15 -0
  39. package/src/Link.tsx +23 -2
  40. package/src/ListColumn.stories.tsx +1 -1
  41. package/src/Select.tsx +3 -0
  42. package/src/eslint/index.test.ts +1 -1
  43. package/src/eslint/no-restyled-part.test.ts +22 -6
  44. package/src/eslint/no-restyled-part.ts +27 -2
  45. package/src/gates/app-checks.ts +14 -0
  46. package/src/gates/create-app.test.ts +7 -2
  47. package/src/looks.ts +6 -4
@@ -32,6 +32,10 @@ import type { Rule, Scope } from 'eslint';
32
32
  *
33
33
  * `EmptyState` takes no padding either (Katerina's ruling 6 of 14 September):
34
34
  * its room comes from the box its rows live in.
35
+ *
36
+ * `Form` takes no layout for its fields (Katerina, 28 September): it stacks
37
+ * them 24px apart itself, so every form reads the same. A form that is not a
38
+ * list of fields says `layout="free"`, and then lays itself out.
35
39
  */
36
40
  /** Where a part sits and how much room it takes. Read on the utility, after its variants. */
37
41
  export declare const PLACEMENT: {
@@ -1 +1 @@
1
- {"version":3,"file":"no-restyled-part.d.ts","sourceRoot":"","sources":["../../src/eslint/no-restyled-part.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"no-restyled-part.d.ts","sourceRoot":"","sources":["../../src/eslint/no-restyled-part.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"app-checks.d.ts","sourceRoot":"","sources":["../../src/gates/app-checks.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAA0B,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE/E,MAAM,WAAW,eAAe;IAC9B,yFAAyF;IACzF,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,2FAA2F;IAC3F,IAAI,EAAE,MAAM,CAAA;IACZ,8GAA8G;IAC9G,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAA;IACvC,+DAA+D;IAC/D,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB,+GAA+G;IAC/G,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAsBpD,CAAA;AAaD,wBAAgB,SAAS,CAAC,CAAC,EAAE,WAAW,EAAE,EAAE,GAAS,EAAE,IAAI,EAAE,KAAsC,EAAE,YAA8D,EAAE,OAAmB,EAAE,EAAE,eAAe,GAAG,UAAU,EAAE,CAyNzN"}
1
+ {"version":3,"file":"app-checks.d.ts","sourceRoot":"","sources":["../../src/gates/app-checks.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAA0B,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE/E,MAAM,WAAW,eAAe;IAC9B,yFAAyF;IACzF,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,2FAA2F;IAC3F,IAAI,EAAE,MAAM,CAAA;IACZ,8GAA8G;IAC9G,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAA;IACvC,+DAA+D;IAC/D,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB,+GAA+G;IAC/G,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAsBpD,CAAA;AAaD,wBAAgB,SAAS,CAAC,CAAC,EAAE,WAAW,EAAE,EAAE,GAAS,EAAE,IAAI,EAAE,KAAsC,EAAE,YAA8D,EAAE,OAAmB,EAAE,EAAE,eAAe,GAAG,UAAU,EAAE,CAuOzN"}
@@ -307,6 +307,20 @@ ${component('<Button title="Delete">x</Button>')}`, "error", "WithTooltip") }
307
307
  return own.length ? h.FAIL(`draws the text tree itself (${own.join(", ")}): pass the tree to RichText`) : h.PASS("formatted text is drawn by RichText only");
308
308
  } }
309
309
  ]),
310
+ // UIG-33, Katerina's picks of 28 September: a row that is one link is `Link` with `cover`, and a
311
+ // chip that is a link is `Chip` with `href`. Before, both apps stretched a link over its row by
312
+ // hand (`after:absolute`) and rounded a Link round a chip, each under an escape.
313
+ ticket("UIG-33", [
314
+ { what: "the installed package has Link cover and Chip href", run: all(h, [
315
+ () => h.contains(at("node_modules/@estiva-app/ui/dist/Link.d.ts"), /\bcover\?: boolean/, "the installed Link takes cover"),
316
+ () => h.contains(at("node_modules/@estiva-app/ui/dist/Chip.d.ts"), /\bhref\?: string/, "the installed Chip takes href")
317
+ ], "the installed Link takes cover and Chip takes href") },
318
+ { what: "no link is stretched over its row by hand", run: () => {
319
+ const sources = h.listFiles(at("src"), (n) => /\.tsx$/.test(n) && !/\.(test|stories)\.tsx$/.test(n));
320
+ const own = sources.filter((f) => /<(?:Link|a)\b[^>]*\bafter:(?:absolute|inset-0)\b/.test(h.read(f)));
321
+ return own.length ? h.FAIL(`stretches a link by hand (${own.join(", ")}): give the Link cover`) : h.PASS("every whole-row link is Link with cover");
322
+ } }
323
+ ]),
310
324
  // Katerina's ruling of 17 September (docs/GATES.md §23): one copy, in the package. An app made
311
325
  // by create-estiva-app passes this from its first commit; Peek and Ship do once UIG-32 lands.
312
326
  ticket("UIG-32", [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/gates/gate-config.ts", "../../src/gates/app-checks.ts"],
4
- "sourcesContent": ["/// <reference types=\"node\" />\n/**\n * Where the UI Guardrails' rules apply, written once (docs/GATES.md \u00A723).\n *\n * Until UIG-10 every repo wrote this itself: `eslint.gates.js` (the rules and\n * where they apply) and `eslint.gates.config.js` (the gate: those rules alone)\n * in Peek, in Ship, and here pointed inward. The rules were always the\n * package's; these two files were copies. Now they are two functions.\n *\n * Where: every `.ts` and `.tsx` under `src`, stories included (a story is what\n * people read and copy), tests not (a test mounts markup to test it) \u2014\n * Katerina's rulings of 13 September; `.ts` since UIG-8. This package's own\n * inward set reads `.tsx` only, as it always has.\n *\n * A place that keeps something says why, on the line above:\n * `// @estiva-escape: <reason>`, or `{/* @estiva-escape: <reason> *\\/}` as a\n * JSX child. Never `eslint-disable`: the count refuses it.\n */\nimport type { ESLint, Linter } from 'eslint'\nimport betterTailwindcss from 'eslint-plugin-better-tailwindcss'\nimport { parser as typescriptParser } from 'typescript-eslint'\nimport estiva from '../eslint/index'\nimport { KNOWN_PLUGINS, TOKEN_LINT_IGNORES } from './token-lint'\nimport { tokenStylePlugin } from './token-style'\n\nexport interface GateConfigOptions {\n /** `app` (the default) runs `configs.recommended`; `package` runs this package's inward set. */\n audience?: 'app' | 'package'\n /**\n * Plugins whose `eslint-disable` directives appear in the source, registered\n * here with every rule off. ESLint refuses a directive for a rule it does not\n * know, so without them the gate would fail on another lint's comments.\n * The token lint's names and TypeScript's are registered already (UIG-37).\n */\n quiet?: Record<string, ESLint.Plugin>\n /** Folders the gate never reads, beside build output. */\n ignores?: string[]\n}\n\n/**\n * The rules and where they apply \u2014 one config object, to add beside\n * everything else an app's `eslint.config.js` runs, so an editor shows a\n * refusal where the element is typed.\n */\nexport function gateLint({ audience = 'app' }: Pick<GateConfigOptions, 'audience'> = {}): Linter.Config {\n return {\n ...(audience === 'package' ? estiva.configs.package : estiva.configs.recommended),\n // An app's every script type: a raw <button> in a .jsx, or a key listener in a .mts,\n // passed while only .ts and .tsx were read (the re-review after the audit before UIG-26).\n files: audience === 'package' ? ['src/**/*.tsx'] : ['src/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'],\n ignores: ['**/*.test.*'],\n languageOptions: {\n parser: typescriptParser,\n parserOptions: { ecmaFeatures: { jsx: true } },\n },\n }\n}\n\n/**\n * The gate: the rules on their own \u2014 `npm run lint:rules`, CI's job `gate`, and\n * the config the editor hook lints a proposed write with. It fails on a gate\n * rule and on nothing else, so a backlog in the full lint never blocks it.\n */\nexport function gateConfig({ audience = 'app', quiet = {}, ignores = [] }: GateConfigOptions = {}): Linter.Config[] {\n return [\n { ignores: [...TOKEN_LINT_IGNORES, ...ignores] },\n {\n plugins: {\n ...KNOWN_PLUGINS,\n ...quiet,\n 'better-tailwindcss': betterTailwindcss,\n 'token-values': betterTailwindcss,\n 'token-spacing': betterTailwindcss,\n 'token-style': tokenStylePlugin,\n },\n // Every other config's directives are unused here, because their rules are\n // off here. Reporting them would be this config complaining about the\n // others' business.\n linterOptions: { reportUnusedDisableDirectives: 'off' },\n },\n gateLint({ audience }),\n ]\n}\n", "/// <reference types=\"node\" />\n/**\n * The gate checks every app runs, written once (docs/GATES.md \u00A723).\n *\n * Until UIG-10 Peek and Ship each wrote them into their own\n * `scripts/gates-checks.mjs`. 65 different checks for finished tickets, counted\n * on 17 September: 28 were identical in both, 21 were the same check written two\n * ways, 2 read each app's real page, and 14 read code only one app has. This is\n * the first three groups as one list. The fourth stays in the app's own checks\n * file, beside this list.\n *\n * Where Peek and Ship wrote one check two ways, the check here runs both, so it\n * passes wherever either did: the raw-link probe with both of their texts, the\n * token probes against each token config the app has.\n *\n * `scripts/gates-compare.mjs` holds this list to Peek's and Ship's: every check\n * of theirs must be here, or be about code only that app has, or the comparison\n * fails and names it.\n *\n * A check for a ticket not built yet is a first guess at the evidence, taken\n * from the ticket's own text; the ticket that builds it changes it here.\n */\nimport type { CheckResult, GateCheck, GateHelpers, GateTicket } from './status'\n\nexport interface AppCheckOptions {\n /** Where the app is inside the repository: `.` unless it sits in a folder of its own. */\n app?: string\n /** A real page of the app, relative to the app, full of elements that are not controls. */\n page: string\n /** The ticket the gate chain is listed under, and whether this repo owns it: UIG-3 in Peek, UIG-4 in Ship. */\n chain?: { ref: string; owner: boolean }\n /** The token lint configs the app has, relative to the app. */\n tokenConfigs?: string[]\n /** The part a hand-made column header should name (UIG-22): `Panel` since 0.37.0, `ContainerHeader` before. */\n headers?: string[]\n}\n\nexport const APP_TICKET_TITLES: Record<string, string> = {\n 'UIG-2': 'The tracking rails \u2014 GATES.md, the status script, the guide committed',\n 'UIG-3': 'Tracer bullet \u2014 one rule, end to end, blocking in Peek',\n 'UIG-4': 'The same chain, blocking in Ship',\n 'UIG-6': 'Branch protection \u2014 the backstop, all three repos',\n 'UIG-7': 'Lint rule \u2014 every remaining raw element',\n 'UIG-8': 'Lint rule \u2014 forbid the reach',\n 'UIG-9': 'Lint rule \u2014 the className allow-list',\n 'UIG-10': 'create-app \u2014 a command that makes a new Estiva app that runs',\n 'UIG-13': \"The registry widens to Peek's 115 and Ship's 74, with classification\",\n 'UIG-19': 'Lock the contract in CI',\n 'UIG-20': 'The Claude skill, reading the registry',\n 'UIG-21': 'CLAUDE.md becomes an index, not a lecture',\n 'UIG-22': 'Fingerprint \u2014 a hand-made header row',\n 'UIG-23': 'Fingerprint \u2014 a hand-made empty state',\n 'UIG-24': 'Fingerprint \u2014 a browser tooltip where ours belongs',\n 'UIG-25': 'Fingerprint \u2014 a component copied out by hand',\n 'UIG-27': 'The components the apps had to build themselves \u2014 Link, ProgressBar, EmptyState padding',\n 'UIG-28': 'Close the two holes in the token contract \u2014 arbitrary values, and inline style',\n 'UIG-30': 'RichText \u2014 one component that draws a message\\'s text, for both apps',\n 'UIG-32': 'Peek and Ship take their gate pieces from the package',\n 'UIG-37': 'The gates refuse a TypeScript eslint-disable comment \u2014 know the rule names, rules off',\n}\n\n/** Run several checks as one: the first that does not pass is the answer. */\nfunction all(h: GateHelpers, steps: (() => CheckResult | Promise<CheckResult>)[], label: string): () => Promise<CheckResult> {\n return async () => {\n for (const step of steps) {\n const r = await step()\n if (r.result !== 'pass') return r\n }\n return h.PASS(label)\n }\n}\n\nexport function appChecks(h: GateHelpers, { app = '.', page, chain = { ref: 'UIG-3', owner: false }, tokenConfigs = ['eslint.tokens.config.js', 'eslint.config.js'], headers = ['Panel'] }: AppCheckOptions): GateTicket[] {\n const WEB = app === '.' ? '' : `${app.replace(/\\/$/, '')}/`\n const cwd = app === '.' ? undefined : app\n const PROBE = 'src/components/__gates_probe__.tsx'\n const GATES = 'eslint.gates.config.js'\n const at = (rel: string) => `${WEB}${rel}`\n const probe = (code: string, expect: 'error' | 'warning' | 'none', mentions?: string, file = PROBE, config = GATES) => () =>\n h.lint({ ...(cwd ? { cwd } : {}), config, file, code, expect, mentions })\n const component = (body: string) => `export function Probe() {\\n return ${body}\\n}\\n`\n const installed = at('node_modules/@estiva-app/ui/dist/index.d.ts')\n const realPage = (): Promise<CheckResult> => h.lint({ ...(cwd ? { cwd } : {}), config: GATES, file: page, code: h.read(at(page)), expect: 'none' })\n const button = component('<button type=\"button\">x</button>')\n /** A line TypeScript's own lint writes; ESLint refuses it where TypeScript's plugin is unknown (UIG-37). */\n const typescriptDirective = '// eslint-disable-next-line @typescript-eslint/no-explicit-any\\nexport const probe: any = 1\\n'\n const ticket = (ref: string, checks: GateCheck[], owner = false): GateTicket => ({ ref, title: APP_TICKET_TITLES[ref], owner, checks })\n\n const tokenProbe = (code: string, expect: 'error' | 'warning' | 'none', mentions?: string, file = PROBE) =>\n all(h, tokenConfigs.map((config) => probe(code, expect, mentions, file, config)), `${tokenConfigs.join(' and ')}: as expected`)\n\n return [\n ticket('UIG-2', [\n { what: 'npm run gates:status is wired', run: () => h.script('package.json', 'gates:status') },\n ]),\n ticket(chain.ref, [\n { what: 'the gate lint config loads', run: () => h.loads(at(GATES)) },\n { what: `${at('package.json')} has lint:rules`, run: () => h.script(at('package.json'), 'lint:rules') },\n { what: 'CI runs lint:rules', run: () => h.ci('lint:rules') },\n { what: 'the committed hook runs the gate lint', run: () => h.hook('.claude/settings.json', 'gates') },\n { what: '.gates-count.json is committed and parses', run: () => h.json(h.exists('.gates-count.json') ? '.gates-count.json' : at('.gates-count.json')) },\n { what: 'a raw <button> is an error naming Button', run: probe(button, 'error', 'Button') },\n { what: 'the same in a story', run: probe(button, 'error', 'Button', 'src/stories/__gates_probe__.stories.tsx') },\n { what: 'a test file is not checked, while source is', run: async () => {\n const source = await probe(button, 'error', 'Button')()\n if (source.result !== 'pass') return h.FAIL(`source is not checked yet, so this proves nothing: ${source.detail}`)\n return probe(button, 'none', undefined, 'src/components/__gates_probe__.test.tsx')()\n } },\n { what: 'the debt list exists', run: () => h.file('docs/GATES-DEBT.md') },\n ], chain.owner),\n ticket('UIG-6', [\n { what: 'GitHub requires the check gate to merge into main', run: () => h.protectedBranch(/^gate$/) },\n { what: \"CI's job gate runs lint:rules\", run: () => h.ciJob('gate', 'lint:rules') },\n ]),\n ticket('UIG-7', [\n { what: 'a raw <input> is an error naming TextInput', run: probe(component('<input />'), 'error', 'TextInput') },\n { what: 'a clickable <div> is an error naming Button (R5)', run: probe(component('<div onClick={() => history.back()}>Back</div>'), 'error', '`Button`') },\n { what: 'a raw <a> is an error naming Link', run: all(h, [probe(component('<a href=\"/topics\">Topics</a>'), 'error', 'Link'), probe(component('<a href=\"/issues\">Issues</a>'), 'error', 'Link')], 'a raw <a> is an error naming Link') },\n { what: 'a raw <form> is an error naming Form, and a file input one naming FilePicker', run: async () => {\n const form = await probe(component('<form />'), 'error', '`Form`')()\n if (form.result !== 'pass') return form\n return probe(component('<input type=\"file\" />'), 'error', 'FilePicker')()\n } },\n { what: '<textarea>, <select>, <dialog> and <label> name Textarea, Select, DialogShell and Field', run: async () => {\n for (const [element, part] of [['textarea', 'Textarea'], ['select', 'Select'], ['dialog', 'DialogShell'], ['label', 'Field']]) {\n const one = await probe(component(`<${element} />`), 'error', `\\`${part}\\``)()\n if (one.result !== 'pass') return one\n }\n return h.PASS('all four are errors, each naming its part')\n } },\n { what: 'an element the package has no part for is an error that says to ask for one', run: probe(component('<iframe title=\"Map\" />'), 'error', 'ask Katerina') },\n { what: 'an escaped element is not an error, while the same element is', run: async () => {\n const raw = await probe(component('<form />'), 'error')()\n if (raw.result !== 'pass') return h.FAIL(`a raw <form> is not caught yet, so this proves nothing: ${raw.detail}`)\n return probe('export function Probe() {\\n return (\\n // @estiva-escape(no-raw-element): a probe that keeps its element on purpose\\n <form />\\n )\\n}\\n', 'none')()\n } },\n { what: 'a real page, full of elements that are not controls, gets no error', run: realPage },\n ]),\n ticket('UIG-8', [\n { what: 'a Base UI import is an error naming the part built on it', run: probe(\"import { Popover } from '@base-ui/react/popover'\\nexport const Probe = Popover\\n\", 'error', '`Popover`') },\n { what: 'createPortal is an error naming the floating parts', run: probe(\"import { createPortal } from 'react-dom'\\nexport const Probe = (to: HTMLElement) => createPortal(<span />, to)\\n\", 'error', '`DialogShell`') },\n { what: 'a key listener on the whole page is an error, in a .ts file too', run: probe(\"export function listen() {\\n window.addEventListener('keydown', () => {})\\n}\\n\", 'error', '`DialogShell`', 'src/lib/__gates_probe__.ts') },\n { what: 'arrow keys by hand are an error; Enter in a field is not', run: async () => {\n const arrows = await probe(\"export const onKeyDown = (e: KeyboardEvent) => e.key === 'ArrowDown'\\n\", 'error', '`Menu`', 'src/lib/__gates_probe__.ts')()\n if (arrows.result !== 'pass') return arrows\n return probe(\"export const onKeyDown = (e: KeyboardEvent) => e.key === 'Enter'\\n\", 'none', undefined, 'src/lib/__gates_probe__.ts')()\n } },\n { what: 'a hand-written role=\"option\" is an error naming Select', run: probe(component('<div role=\"option\" />'), 'error', '`Select`') },\n { what: 'tabIndex={0} on a div is an error; tabIndex={-1} is not', run: async () => {\n const zero = await probe(component('<div tabIndex={0} />'), 'error', '`Button`')()\n if (zero.result !== 'pass') return h.FAIL(`tabIndex={0} is not caught yet, so this proves nothing: ${zero.detail}`)\n return probe(component('<div tabIndex={-1} />'), 'none')()\n } },\n { what: 'overflow-y-auto is an error naming ScrollArea', run: probe(component('<div className=\"h-64 overflow-y-auto\" />'), 'error', '`ScrollArea`') },\n { what: 'an escaped listener is not an error, while the same listener is', run: async () => {\n const raw = await probe(\"export function listen() {\\n document.addEventListener('mousedown', () => {})\\n}\\n\", 'error', undefined, 'src/lib/__gates_probe__.ts')()\n if (raw.result !== 'pass') return h.FAIL(`a page listener is not caught yet, so this proves nothing: ${raw.detail}`)\n return probe(\"export function listen() {\\n // @estiva-escape(no-rebuilt-behaviour): a probe that keeps its listener on purpose\\n document.addEventListener('mousedown', () => {})\\n}\\n\", 'none', undefined, 'src/lib/__gates_probe__.ts')()\n } },\n { what: 'a real page gets no error', run: realPage },\n ]),\n ticket('UIG-9', [\n { what: 'a border passed into Button is an error naming Button', run: probe(`import { Button } from '@estiva-app/ui'\\n${component('<Button className=\"border\">x</Button>')}`, 'error', 'on `Button` changes how it looks') },\n { what: 'placement passed into Button is not', run: probe(`import { Button } from '@estiva-app/ui'\\n${component('<Button className=\"mt-2 w-full flex-1 relative\">x</Button>')}`, 'none') },\n { what: 'an escaped look is not an error, while the same look is', run: async () => {\n const raw = await probe(`import { Link } from '@estiva-app/ui'\\n${component('<Link href=\"/x\" className=\"after:absolute after:inset-0\">x</Link>')}`, 'error', '`Link`')()\n if (raw.result !== 'pass') return h.FAIL(`a restyled part is not caught yet, so this proves nothing: ${raw.detail}`)\n return probe(`import { Link } from '@estiva-app/ui'\\nexport function Probe() {\\n return (\\n // @estiva-escape(no-restyled-part): a probe that keeps its row link on purpose\\n <Link href=\"/x\" className=\"after:absolute after:inset-0\">x</Link>\\n )\\n}\\n`, 'none')()\n } },\n { what: 'a real page gets no error', run: realPage },\n ]),\n // UIG-10, reopened 18 September: a tab holds one relay client, the way Peek and Ship hold\n // theirs and a made app is written to. Measured on both mains that day: one each, and no\n // socket opened any other way. A second holder, or a socket of the app's own, is a second\n // connection the relay signs in and budgets separately.\n ticket('UIG-10', [\n { what: 'the app holds one relay client, at module level, and opens no socket of its own', run: () => {\n const sources = h.listFiles(at('src'), (n) => /\\.tsx?$/.test(n) && !/\\.(test|stories)\\.tsx?$/.test(n))\n const own = sources.filter((f) => /\\bcreateLiveClient\\(|\\bcreateLiveRelay\\(|\\bnew WebSocket\\(/.test(h.read(f)))\n if (own.length) return h.FAIL(`opens a socket of its own (${own.join(', ')}): hold one client from createLiveClientHolder()`)\n const holders = sources.filter((f) => /^\\s*(?:export\\s+)?const\\s+\\w+\\s*=\\s*createLiveClientHolder\\(\\)/m.test(h.read(f)))\n if (holders.length !== 1) return h.FAIL(holders.length ? `${holders.length} client holders (${holders.join(', ')}): a tab holds one` : 'no module-level createLiveClientHolder(): the app has no relay client')\n return h.PASS(`one client for the tab, held in ${holders[0]}`)\n } },\n ]),\n // Built by UIG-13, 18 September. An app's catalogue is built fresh from its code\n // and never committed: the package is public and the apps are private (Katerina).\n ticket('UIG-13', [\n { what: \"npm run ui:find is the package's search, over the package and this app\", run: () => h.contains(at('package.json'), /\"ui:find\":\\s*\"estiva-ui find\\b/, \"ui:find runs the package's estiva-ui find\") },\n { what: \"npm run registry:check is the package's check\", run: () => h.contains(at('package.json'), /\"registry:check\":\\s*\"estiva-ui check\\b/, 'registry:check runs estiva-ui check') },\n { what: \"CI's job gate refuses a part with no description\", run: () => h.ciJob('gate', 'registry:check') },\n // In the .gitignore of the app's own folder, where `estiva-ui build` would write it.\n { what: 'the catalogue is never committed: git ignores registry.json', run: () => h.contains(at('.gitignore'), /^\\/?registry\\.json\\r?$/m, `${at('.gitignore')} ignores registry.json`) },\n { what: 'every part is described and sorted, and every file is accounted for', run: () => h.catalogue(at('.')) },\n ]),\n ticket('UIG-19', [\n // The contract runs inside `estiva-ui check`, which the gate job already runs\n // (UIG-13), so an app changes no script: it takes the package that has it.\n { what: 'the installed package checks the usage-page contract and the story links', run: () => h.contains(at('node_modules/@estiva-app/ui/dist/registry/cli.js'), 'the usage-page contract', \"the installed estiva-ui check runs the contract\") },\n { what: \"CI's job gate runs it\", run: () => h.ciJob('gate', 'registry:check') },\n { what: 'the app keeps no copy of it in its own gates-checks.mjs', run: () => h.exists(at('scripts/gates-checks.mjs')) ? h.lacks(at('scripts/gates-checks.mjs'), '\"When not\"', 'the app checks no page sections itself') : h.PASS('the app has no gates-checks.mjs') },\n ]),\n // The Claude skill (UIG-20): its text is the installed package's, read in by a\n // loader `estiva-ui skill` writes; the app commits the loader and the settings.\n ticket('UIG-20', [\n { what: \"the skill's loader reads the installed package's text\", run: () => h.contains('.claude/skills/estiva-ui/SKILL.md', `!\\`cat \"\\${CLAUDE_PROJECT_DIR}/${at('node_modules/@estiva-app/ui/skill/estiva-ui.md')}\"\\``, 'the loader reads the package\u2019s skill') },\n { what: 'the search runs without a prompt', run: () => h.contains('.claude/settings.json', 'Bash(npm run ui:find *)', '.claude/settings.json allows npm run ui:find') },\n { what: 'ui:find searches every app beside this one: it names none', run: () => h.lacks(at('package.json'), /\"ui:find\":[^\\n]*--also/, 'ui:find names no neighbour, so it finds them all') },\n ...(WEB ? [{ what: 'ui:find runs from the top folder too', run: () => h.contains('package.json', /\"ui:find\":/, 'the top folder has ui:find') }] : []),\n { what: \"CI's job gate refuses a loader that differs\", run: () => (h.exists(at('node_modules/@estiva-app/ui/dist/registry/cli.js')) && /claude skill:/.test(h.read(at('node_modules/@estiva-app/ui/dist/registry/cli.js'))) ? h.ciJob('gate', 'registry:check') : h.FAIL('the installed package does not check the loader: take a version that does')) },\n ]),\n // What loads into every Claude session (UIG-21): CLAUDE.md is an index, the\n // rest loads where it applies, and a rule a check can hold is a check.\n ticket('UIG-21', [\n { what: 'CLAUDE.md with its imports is under 200 lines, and every rule file is scoped', run: () => h.instructions(200) },\n { what: \"CI's job gate runs the token lint\", run: () => {\n const tokens = h.ciJob('gate', 'lint:tokens')\n return tokens.result === 'pass' ? tokens : h.ciJob('gate', 'lint')\n } },\n { what: \"a plain tailwind-merge is an error naming the package's cn()\", run: tokenProbe(\"import { twMerge } from 'tailwind-merge'\\nexport const merged = twMerge('p-2', 'p-3')\\n\", 'error', 'cn()', 'src/lib/__gates_probe__.ts') },\n ]),\n ticket('UIG-22', [\n // The Folders pane's own row, at the commit before peek 3dc663b.\n { what: `a hand-made column header is an error naming ${headers.join(' or ')}`, run: all(h, headers.map((name) => probe(component('<div className=\"flex h-full flex-col\"><div className=\"flex items-center gap-2 px-3 py-2\"><span>Folders</span></div><div /></div>'), 'error', name)), `a hand-made column header is an error naming ${headers.join(' or ')}`) },\n // 26 September: the package's header on its own is a panel made by hand, whose box forgot to scroll.\n { what: \"the package's ContainerHeader on its own is an error naming Panel\", run: probe(`import { ContainerHeader } from '@estiva-app/ui'\\n${component('<div className=\"flex flex-col\"><ContainerHeader title=\"Details\" /><div className=\"px-4\">x</div></div>')}`, 'error', '`Panel`') },\n { what: 'a group heading inside a list is left alone', run: probe(component('<section className=\"flex flex-col gap-px\"><h3 className=\"flex h-8 items-center gap-2 px-3\"><span>Todo</span></h3><div /></section>'), 'none') },\n ]),\n ticket('UIG-23', [\n { what: 'a hand-made empty line is an error naming EmptyState', run: probe(\"export function Probe({ items }: { items: string[] }) {\\n return <div>{items.length === 0 ? <p className=\\\"text-text-secondary\\\">Nothing here</p> : items.map((i) => <span key={i}>{i}</span>)}</div>\\n}\\n\", 'error', 'EmptyState') },\n ]),\n ticket('UIG-24', [\n { what: 'a title= is an error naming WithTooltip', run: probe(`import { Button } from '@estiva-app/ui'\\n${component('<Button title=\"Delete\">x</Button>')}`, 'error', 'WithTooltip') },\n ]),\n ticket('UIG-25', [\n { what: \"SectionLabel's class list, typed by hand, is a warning\", run: probe(component('<span className=\"text-h5 leading-3 signal:font-mono signal:text-small signal:uppercase signal:tracking-widest text-text-secondary\">Label</span>'), 'warning', 'SectionLabel') },\n ]),\n ticket('UIG-27', [\n { what: 'the installed package has Link', run: () => h.contains(installed, /\\bLink\\b/, 'the installed @estiva-app/ui exports Link') },\n { what: 'the installed package has ProgressBar', run: () => h.contains(installed, /\\bProgressBar\\b/, 'the installed @estiva-app/ui exports ProgressBar') },\n ]),\n ticket('UIG-28', [\n { what: 'text-[14px] is an error', run: tokenProbe(component('<div className=\"text-[14px]\">x</div>'), 'error') },\n { what: 'a colour in an inline style is an error', run: tokenProbe(component(\"<div style={{ backgroundColor: '#ff0000' }}>x</div>\"), 'error') },\n { what: 'h-[240px] is a warning', run: tokenProbe(component('<div className=\"h-[240px]\">x</div>'), 'warning') },\n { what: 'h-[240px] is not an error', run: tokenProbe(component('<div className=\"h-[240px]\">x</div>'), 'none') },\n { what: 'a hand-written line height behind an arbitrary variant is an error', run: tokenProbe(component('<div className=\"[&_p]:leading-[1.4]\">x</div>'), 'error') },\n { what: 'a raw colour in a class is an error (the pattern that matched nothing, R1)', run: tokenProbe(component('<div className=\"bg-[#5c69dc]\">x</div>'), 'error', 'raw colour') },\n { what: 'a width and height from a prop pass', run: tokenProbe(\"export function Probe({ size }: { size: number }) {\\n return <div style={{ width: size, height: size }}>x</div>\\n}\\n\", 'none') },\n { what: 'a test file is not checked for hand-written values, while source is', run: all(h, tokenConfigs.map((config) => async () => {\n const code = component(\"<div className=\\\"text-[14px]\\\" style={{ color: 'red' }}>x</div>\")\n const source = await probe(code, 'error', undefined, PROBE, config)()\n if (source.result !== 'pass') return h.FAIL(`source is not checked yet, so this proves nothing: ${source.detail}`)\n return probe(code, 'none', undefined, 'src/components/__gates_probe__.test.tsx', config)()\n }), 'a test file is not checked for hand-written values, while source is') },\n ]),\n // Built 28 September: both apps drew the protocol's text tree by hand, and drifted apart.\n // A file that reads the protocol and draws a quote or a numbered list itself is a third copy.\n ticket('UIG-30', [\n { what: 'the installed package has RichText', run: () => h.contains(installed, /\\bRichText\\b/, 'the installed @estiva-app/ui exports RichText') },\n { what: \"no file draws the protocol's text tree by hand\", run: () => {\n const sources = h.listFiles(at('src'), (n) => /\\.tsx$/.test(n) && !/\\.(test|stories)\\.tsx$/.test(n))\n const own = sources.filter((f) => { const text = h.read(f); return /from ['\"]@estiva-app\\/protocol['\"]/.test(text) && /<(?:blockquote|ol)\\b/.test(text) })\n return own.length ? h.FAIL(`draws the text tree itself (${own.join(', ')}): pass the tree to RichText`) : h.PASS('formatted text is drawn by RichText only')\n } },\n ]),\n // Katerina's ruling of 17 September (docs/GATES.md \u00A723): one copy, in the package. An app made\n // by create-estiva-app passes this from its first commit; Peek and Ship do once UIG-32 lands.\n ticket('UIG-32', [\n { what: 'the gate pieces come from the package, and no repo keeps a copy', run: all(h, [\n () => h.contains(at('eslint.gates.config.js'), /from '@estiva-app\\/ui\\/gates'/, 'the gate config imports @estiva-app/ui/gates'),\n () => h.hook('.claude/settings.json', '@estiva-app/ui/dist/gates/cli.js'),\n // `estiva-gates status` where npm can find the command; by path where it cannot,\n // because the install is in the app's folder and the script is in the repo's top one (Ship).\n () => h.contains('package.json', /\"gates:status\":\\s*\"[^\"]*(?:estiva-gates|@estiva-app\\/ui\\/dist\\/gates\\/cli\\.js)[^\"]*status/, \"gates:status runs the package's engine\"),\n () => (h.exists('scripts/gates-status.mjs') ? h.FAIL('scripts/gates-status.mjs is a copy of the status engine: run estiva-gates status instead') : h.PASS('no copy of the status engine')),\n ], 'the gate config, the hook and gates:status are the package\\'s; no copy of the engine') },\n ]),\n ticket('UIG-37', [\n { what: 'a TypeScript eslint-disable comment breaks neither the gate nor the token lint', run: all(h, [\n probe(typescriptDirective, 'none'),\n tokenProbe(typescriptDirective, 'none'),\n ], `${[GATES, ...tokenConfigs].join(', ')}: a TypeScript directive is no error`) },\n { what: 'a token lint on its own is the package\\'s tokenConfig, not built by hand', run: () =>\n (h.exists(at('eslint.tokens.config.js'))\n ? h.contains(at('eslint.tokens.config.js'), /\\btokenConfig\\(/, 'eslint.tokens.config.js runs tokenConfig from @estiva-app/ui/gates')\n : h.PASS('no token lint on its own')) },\n ]),\n ]\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/// <reference types=\"node\" />\n/**\n * Where the UI Guardrails' rules apply, written once (docs/GATES.md \u00A723).\n *\n * Until UIG-10 every repo wrote this itself: `eslint.gates.js` (the rules and\n * where they apply) and `eslint.gates.config.js` (the gate: those rules alone)\n * in Peek, in Ship, and here pointed inward. The rules were always the\n * package's; these two files were copies. Now they are two functions.\n *\n * Where: every `.ts` and `.tsx` under `src`, stories included (a story is what\n * people read and copy), tests not (a test mounts markup to test it) \u2014\n * Katerina's rulings of 13 September; `.ts` since UIG-8. This package's own\n * inward set reads `.tsx` only, as it always has.\n *\n * A place that keeps something says why, on the line above:\n * `// @estiva-escape: <reason>`, or `{/* @estiva-escape: <reason> *\\/}` as a\n * JSX child. Never `eslint-disable`: the count refuses it.\n */\nimport type { ESLint, Linter } from 'eslint'\nimport betterTailwindcss from 'eslint-plugin-better-tailwindcss'\nimport { parser as typescriptParser } from 'typescript-eslint'\nimport estiva from '../eslint/index'\nimport { KNOWN_PLUGINS, TOKEN_LINT_IGNORES } from './token-lint'\nimport { tokenStylePlugin } from './token-style'\n\nexport interface GateConfigOptions {\n /** `app` (the default) runs `configs.recommended`; `package` runs this package's inward set. */\n audience?: 'app' | 'package'\n /**\n * Plugins whose `eslint-disable` directives appear in the source, registered\n * here with every rule off. ESLint refuses a directive for a rule it does not\n * know, so without them the gate would fail on another lint's comments.\n * The token lint's names and TypeScript's are registered already (UIG-37).\n */\n quiet?: Record<string, ESLint.Plugin>\n /** Folders the gate never reads, beside build output. */\n ignores?: string[]\n}\n\n/**\n * The rules and where they apply \u2014 one config object, to add beside\n * everything else an app's `eslint.config.js` runs, so an editor shows a\n * refusal where the element is typed.\n */\nexport function gateLint({ audience = 'app' }: Pick<GateConfigOptions, 'audience'> = {}): Linter.Config {\n return {\n ...(audience === 'package' ? estiva.configs.package : estiva.configs.recommended),\n // An app's every script type: a raw <button> in a .jsx, or a key listener in a .mts,\n // passed while only .ts and .tsx were read (the re-review after the audit before UIG-26).\n files: audience === 'package' ? ['src/**/*.tsx'] : ['src/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'],\n ignores: ['**/*.test.*'],\n languageOptions: {\n parser: typescriptParser,\n parserOptions: { ecmaFeatures: { jsx: true } },\n },\n }\n}\n\n/**\n * The gate: the rules on their own \u2014 `npm run lint:rules`, CI's job `gate`, and\n * the config the editor hook lints a proposed write with. It fails on a gate\n * rule and on nothing else, so a backlog in the full lint never blocks it.\n */\nexport function gateConfig({ audience = 'app', quiet = {}, ignores = [] }: GateConfigOptions = {}): Linter.Config[] {\n return [\n { ignores: [...TOKEN_LINT_IGNORES, ...ignores] },\n {\n plugins: {\n ...KNOWN_PLUGINS,\n ...quiet,\n 'better-tailwindcss': betterTailwindcss,\n 'token-values': betterTailwindcss,\n 'token-spacing': betterTailwindcss,\n 'token-style': tokenStylePlugin,\n },\n // Every other config's directives are unused here, because their rules are\n // off here. Reporting them would be this config complaining about the\n // others' business.\n linterOptions: { reportUnusedDisableDirectives: 'off' },\n },\n gateLint({ audience }),\n ]\n}\n", "/// <reference types=\"node\" />\n/**\n * The gate checks every app runs, written once (docs/GATES.md \u00A723).\n *\n * Until UIG-10 Peek and Ship each wrote them into their own\n * `scripts/gates-checks.mjs`. 65 different checks for finished tickets, counted\n * on 17 September: 28 were identical in both, 21 were the same check written two\n * ways, 2 read each app's real page, and 14 read code only one app has. This is\n * the first three groups as one list. The fourth stays in the app's own checks\n * file, beside this list.\n *\n * Where Peek and Ship wrote one check two ways, the check here runs both, so it\n * passes wherever either did: the raw-link probe with both of their texts, the\n * token probes against each token config the app has.\n *\n * `scripts/gates-compare.mjs` holds this list to Peek's and Ship's: every check\n * of theirs must be here, or be about code only that app has, or the comparison\n * fails and names it.\n *\n * A check for a ticket not built yet is a first guess at the evidence, taken\n * from the ticket's own text; the ticket that builds it changes it here.\n */\nimport type { CheckResult, GateCheck, GateHelpers, GateTicket } from './status'\n\nexport interface AppCheckOptions {\n /** Where the app is inside the repository: `.` unless it sits in a folder of its own. */\n app?: string\n /** A real page of the app, relative to the app, full of elements that are not controls. */\n page: string\n /** The ticket the gate chain is listed under, and whether this repo owns it: UIG-3 in Peek, UIG-4 in Ship. */\n chain?: { ref: string; owner: boolean }\n /** The token lint configs the app has, relative to the app. */\n tokenConfigs?: string[]\n /** The part a hand-made column header should name (UIG-22): `Panel` since 0.37.0, `ContainerHeader` before. */\n headers?: string[]\n}\n\nexport const APP_TICKET_TITLES: Record<string, string> = {\n 'UIG-2': 'The tracking rails \u2014 GATES.md, the status script, the guide committed',\n 'UIG-3': 'Tracer bullet \u2014 one rule, end to end, blocking in Peek',\n 'UIG-4': 'The same chain, blocking in Ship',\n 'UIG-6': 'Branch protection \u2014 the backstop, all three repos',\n 'UIG-7': 'Lint rule \u2014 every remaining raw element',\n 'UIG-8': 'Lint rule \u2014 forbid the reach',\n 'UIG-9': 'Lint rule \u2014 the className allow-list',\n 'UIG-10': 'create-app \u2014 a command that makes a new Estiva app that runs',\n 'UIG-13': \"The registry widens to Peek's 115 and Ship's 74, with classification\",\n 'UIG-19': 'Lock the contract in CI',\n 'UIG-20': 'The Claude skill, reading the registry',\n 'UIG-21': 'CLAUDE.md becomes an index, not a lecture',\n 'UIG-22': 'Fingerprint \u2014 a hand-made header row',\n 'UIG-23': 'Fingerprint \u2014 a hand-made empty state',\n 'UIG-24': 'Fingerprint \u2014 a browser tooltip where ours belongs',\n 'UIG-25': 'Fingerprint \u2014 a component copied out by hand',\n 'UIG-27': 'The components the apps had to build themselves \u2014 Link, ProgressBar, EmptyState padding',\n 'UIG-28': 'Close the two holes in the token contract \u2014 arbitrary values, and inline style',\n 'UIG-30': 'RichText \u2014 one component that draws a message\\'s text, for both apps',\n 'UIG-32': 'Peek and Ship take their gate pieces from the package',\n 'UIG-37': 'The gates refuse a TypeScript eslint-disable comment \u2014 know the rule names, rules off',\n}\n\n/** Run several checks as one: the first that does not pass is the answer. */\nfunction all(h: GateHelpers, steps: (() => CheckResult | Promise<CheckResult>)[], label: string): () => Promise<CheckResult> {\n return async () => {\n for (const step of steps) {\n const r = await step()\n if (r.result !== 'pass') return r\n }\n return h.PASS(label)\n }\n}\n\nexport function appChecks(h: GateHelpers, { app = '.', page, chain = { ref: 'UIG-3', owner: false }, tokenConfigs = ['eslint.tokens.config.js', 'eslint.config.js'], headers = ['Panel'] }: AppCheckOptions): GateTicket[] {\n const WEB = app === '.' ? '' : `${app.replace(/\\/$/, '')}/`\n const cwd = app === '.' ? undefined : app\n const PROBE = 'src/components/__gates_probe__.tsx'\n const GATES = 'eslint.gates.config.js'\n const at = (rel: string) => `${WEB}${rel}`\n const probe = (code: string, expect: 'error' | 'warning' | 'none', mentions?: string, file = PROBE, config = GATES) => () =>\n h.lint({ ...(cwd ? { cwd } : {}), config, file, code, expect, mentions })\n const component = (body: string) => `export function Probe() {\\n return ${body}\\n}\\n`\n const installed = at('node_modules/@estiva-app/ui/dist/index.d.ts')\n const realPage = (): Promise<CheckResult> => h.lint({ ...(cwd ? { cwd } : {}), config: GATES, file: page, code: h.read(at(page)), expect: 'none' })\n const button = component('<button type=\"button\">x</button>')\n /** A line TypeScript's own lint writes; ESLint refuses it where TypeScript's plugin is unknown (UIG-37). */\n const typescriptDirective = '// eslint-disable-next-line @typescript-eslint/no-explicit-any\\nexport const probe: any = 1\\n'\n const ticket = (ref: string, checks: GateCheck[], owner = false): GateTicket => ({ ref, title: APP_TICKET_TITLES[ref], owner, checks })\n\n const tokenProbe = (code: string, expect: 'error' | 'warning' | 'none', mentions?: string, file = PROBE) =>\n all(h, tokenConfigs.map((config) => probe(code, expect, mentions, file, config)), `${tokenConfigs.join(' and ')}: as expected`)\n\n return [\n ticket('UIG-2', [\n { what: 'npm run gates:status is wired', run: () => h.script('package.json', 'gates:status') },\n ]),\n ticket(chain.ref, [\n { what: 'the gate lint config loads', run: () => h.loads(at(GATES)) },\n { what: `${at('package.json')} has lint:rules`, run: () => h.script(at('package.json'), 'lint:rules') },\n { what: 'CI runs lint:rules', run: () => h.ci('lint:rules') },\n { what: 'the committed hook runs the gate lint', run: () => h.hook('.claude/settings.json', 'gates') },\n { what: '.gates-count.json is committed and parses', run: () => h.json(h.exists('.gates-count.json') ? '.gates-count.json' : at('.gates-count.json')) },\n { what: 'a raw <button> is an error naming Button', run: probe(button, 'error', 'Button') },\n { what: 'the same in a story', run: probe(button, 'error', 'Button', 'src/stories/__gates_probe__.stories.tsx') },\n { what: 'a test file is not checked, while source is', run: async () => {\n const source = await probe(button, 'error', 'Button')()\n if (source.result !== 'pass') return h.FAIL(`source is not checked yet, so this proves nothing: ${source.detail}`)\n return probe(button, 'none', undefined, 'src/components/__gates_probe__.test.tsx')()\n } },\n { what: 'the debt list exists', run: () => h.file('docs/GATES-DEBT.md') },\n ], chain.owner),\n ticket('UIG-6', [\n { what: 'GitHub requires the check gate to merge into main', run: () => h.protectedBranch(/^gate$/) },\n { what: \"CI's job gate runs lint:rules\", run: () => h.ciJob('gate', 'lint:rules') },\n ]),\n ticket('UIG-7', [\n { what: 'a raw <input> is an error naming TextInput', run: probe(component('<input />'), 'error', 'TextInput') },\n { what: 'a clickable <div> is an error naming Button (R5)', run: probe(component('<div onClick={() => history.back()}>Back</div>'), 'error', '`Button`') },\n { what: 'a raw <a> is an error naming Link', run: all(h, [probe(component('<a href=\"/topics\">Topics</a>'), 'error', 'Link'), probe(component('<a href=\"/issues\">Issues</a>'), 'error', 'Link')], 'a raw <a> is an error naming Link') },\n { what: 'a raw <form> is an error naming Form, and a file input one naming FilePicker', run: async () => {\n const form = await probe(component('<form />'), 'error', '`Form`')()\n if (form.result !== 'pass') return form\n return probe(component('<input type=\"file\" />'), 'error', 'FilePicker')()\n } },\n { what: '<textarea>, <select>, <dialog> and <label> name Textarea, Select, DialogShell and Field', run: async () => {\n for (const [element, part] of [['textarea', 'Textarea'], ['select', 'Select'], ['dialog', 'DialogShell'], ['label', 'Field']]) {\n const one = await probe(component(`<${element} />`), 'error', `\\`${part}\\``)()\n if (one.result !== 'pass') return one\n }\n return h.PASS('all four are errors, each naming its part')\n } },\n { what: 'an element the package has no part for is an error that says to ask for one', run: probe(component('<iframe title=\"Map\" />'), 'error', 'ask Katerina') },\n { what: 'an escaped element is not an error, while the same element is', run: async () => {\n const raw = await probe(component('<form />'), 'error')()\n if (raw.result !== 'pass') return h.FAIL(`a raw <form> is not caught yet, so this proves nothing: ${raw.detail}`)\n return probe('export function Probe() {\\n return (\\n // @estiva-escape(no-raw-element): a probe that keeps its element on purpose\\n <form />\\n )\\n}\\n', 'none')()\n } },\n { what: 'a real page, full of elements that are not controls, gets no error', run: realPage },\n ]),\n ticket('UIG-8', [\n { what: 'a Base UI import is an error naming the part built on it', run: probe(\"import { Popover } from '@base-ui/react/popover'\\nexport const Probe = Popover\\n\", 'error', '`Popover`') },\n { what: 'createPortal is an error naming the floating parts', run: probe(\"import { createPortal } from 'react-dom'\\nexport const Probe = (to: HTMLElement) => createPortal(<span />, to)\\n\", 'error', '`DialogShell`') },\n { what: 'a key listener on the whole page is an error, in a .ts file too', run: probe(\"export function listen() {\\n window.addEventListener('keydown', () => {})\\n}\\n\", 'error', '`DialogShell`', 'src/lib/__gates_probe__.ts') },\n { what: 'arrow keys by hand are an error; Enter in a field is not', run: async () => {\n const arrows = await probe(\"export const onKeyDown = (e: KeyboardEvent) => e.key === 'ArrowDown'\\n\", 'error', '`Menu`', 'src/lib/__gates_probe__.ts')()\n if (arrows.result !== 'pass') return arrows\n return probe(\"export const onKeyDown = (e: KeyboardEvent) => e.key === 'Enter'\\n\", 'none', undefined, 'src/lib/__gates_probe__.ts')()\n } },\n { what: 'a hand-written role=\"option\" is an error naming Select', run: probe(component('<div role=\"option\" />'), 'error', '`Select`') },\n { what: 'tabIndex={0} on a div is an error; tabIndex={-1} is not', run: async () => {\n const zero = await probe(component('<div tabIndex={0} />'), 'error', '`Button`')()\n if (zero.result !== 'pass') return h.FAIL(`tabIndex={0} is not caught yet, so this proves nothing: ${zero.detail}`)\n return probe(component('<div tabIndex={-1} />'), 'none')()\n } },\n { what: 'overflow-y-auto is an error naming ScrollArea', run: probe(component('<div className=\"h-64 overflow-y-auto\" />'), 'error', '`ScrollArea`') },\n { what: 'an escaped listener is not an error, while the same listener is', run: async () => {\n const raw = await probe(\"export function listen() {\\n document.addEventListener('mousedown', () => {})\\n}\\n\", 'error', undefined, 'src/lib/__gates_probe__.ts')()\n if (raw.result !== 'pass') return h.FAIL(`a page listener is not caught yet, so this proves nothing: ${raw.detail}`)\n return probe(\"export function listen() {\\n // @estiva-escape(no-rebuilt-behaviour): a probe that keeps its listener on purpose\\n document.addEventListener('mousedown', () => {})\\n}\\n\", 'none', undefined, 'src/lib/__gates_probe__.ts')()\n } },\n { what: 'a real page gets no error', run: realPage },\n ]),\n ticket('UIG-9', [\n { what: 'a border passed into Button is an error naming Button', run: probe(`import { Button } from '@estiva-app/ui'\\n${component('<Button className=\"border\">x</Button>')}`, 'error', 'on `Button` changes how it looks') },\n { what: 'placement passed into Button is not', run: probe(`import { Button } from '@estiva-app/ui'\\n${component('<Button className=\"mt-2 w-full flex-1 relative\">x</Button>')}`, 'none') },\n { what: 'an escaped look is not an error, while the same look is', run: async () => {\n const raw = await probe(`import { Link } from '@estiva-app/ui'\\n${component('<Link href=\"/x\" className=\"after:absolute after:inset-0\">x</Link>')}`, 'error', '`Link`')()\n if (raw.result !== 'pass') return h.FAIL(`a restyled part is not caught yet, so this proves nothing: ${raw.detail}`)\n return probe(`import { Link } from '@estiva-app/ui'\\nexport function Probe() {\\n return (\\n // @estiva-escape(no-restyled-part): a probe that keeps its row link on purpose\\n <Link href=\"/x\" className=\"after:absolute after:inset-0\">x</Link>\\n )\\n}\\n`, 'none')()\n } },\n { what: 'a real page gets no error', run: realPage },\n ]),\n // UIG-10, reopened 18 September: a tab holds one relay client, the way Peek and Ship hold\n // theirs and a made app is written to. Measured on both mains that day: one each, and no\n // socket opened any other way. A second holder, or a socket of the app's own, is a second\n // connection the relay signs in and budgets separately.\n ticket('UIG-10', [\n { what: 'the app holds one relay client, at module level, and opens no socket of its own', run: () => {\n const sources = h.listFiles(at('src'), (n) => /\\.tsx?$/.test(n) && !/\\.(test|stories)\\.tsx?$/.test(n))\n const own = sources.filter((f) => /\\bcreateLiveClient\\(|\\bcreateLiveRelay\\(|\\bnew WebSocket\\(/.test(h.read(f)))\n if (own.length) return h.FAIL(`opens a socket of its own (${own.join(', ')}): hold one client from createLiveClientHolder()`)\n const holders = sources.filter((f) => /^\\s*(?:export\\s+)?const\\s+\\w+\\s*=\\s*createLiveClientHolder\\(\\)/m.test(h.read(f)))\n if (holders.length !== 1) return h.FAIL(holders.length ? `${holders.length} client holders (${holders.join(', ')}): a tab holds one` : 'no module-level createLiveClientHolder(): the app has no relay client')\n return h.PASS(`one client for the tab, held in ${holders[0]}`)\n } },\n ]),\n // Built by UIG-13, 18 September. An app's catalogue is built fresh from its code\n // and never committed: the package is public and the apps are private (Katerina).\n ticket('UIG-13', [\n { what: \"npm run ui:find is the package's search, over the package and this app\", run: () => h.contains(at('package.json'), /\"ui:find\":\\s*\"estiva-ui find\\b/, \"ui:find runs the package's estiva-ui find\") },\n { what: \"npm run registry:check is the package's check\", run: () => h.contains(at('package.json'), /\"registry:check\":\\s*\"estiva-ui check\\b/, 'registry:check runs estiva-ui check') },\n { what: \"CI's job gate refuses a part with no description\", run: () => h.ciJob('gate', 'registry:check') },\n // In the .gitignore of the app's own folder, where `estiva-ui build` would write it.\n { what: 'the catalogue is never committed: git ignores registry.json', run: () => h.contains(at('.gitignore'), /^\\/?registry\\.json\\r?$/m, `${at('.gitignore')} ignores registry.json`) },\n { what: 'every part is described and sorted, and every file is accounted for', run: () => h.catalogue(at('.')) },\n ]),\n ticket('UIG-19', [\n // The contract runs inside `estiva-ui check`, which the gate job already runs\n // (UIG-13), so an app changes no script: it takes the package that has it.\n { what: 'the installed package checks the usage-page contract and the story links', run: () => h.contains(at('node_modules/@estiva-app/ui/dist/registry/cli.js'), 'the usage-page contract', \"the installed estiva-ui check runs the contract\") },\n { what: \"CI's job gate runs it\", run: () => h.ciJob('gate', 'registry:check') },\n { what: 'the app keeps no copy of it in its own gates-checks.mjs', run: () => h.exists(at('scripts/gates-checks.mjs')) ? h.lacks(at('scripts/gates-checks.mjs'), '\"When not\"', 'the app checks no page sections itself') : h.PASS('the app has no gates-checks.mjs') },\n ]),\n // The Claude skill (UIG-20): its text is the installed package's, read in by a\n // loader `estiva-ui skill` writes; the app commits the loader and the settings.\n ticket('UIG-20', [\n { what: \"the skill's loader reads the installed package's text\", run: () => h.contains('.claude/skills/estiva-ui/SKILL.md', `!\\`cat \"\\${CLAUDE_PROJECT_DIR}/${at('node_modules/@estiva-app/ui/skill/estiva-ui.md')}\"\\``, 'the loader reads the package\u2019s skill') },\n { what: 'the search runs without a prompt', run: () => h.contains('.claude/settings.json', 'Bash(npm run ui:find *)', '.claude/settings.json allows npm run ui:find') },\n { what: 'ui:find searches every app beside this one: it names none', run: () => h.lacks(at('package.json'), /\"ui:find\":[^\\n]*--also/, 'ui:find names no neighbour, so it finds them all') },\n ...(WEB ? [{ what: 'ui:find runs from the top folder too', run: () => h.contains('package.json', /\"ui:find\":/, 'the top folder has ui:find') }] : []),\n { what: \"CI's job gate refuses a loader that differs\", run: () => (h.exists(at('node_modules/@estiva-app/ui/dist/registry/cli.js')) && /claude skill:/.test(h.read(at('node_modules/@estiva-app/ui/dist/registry/cli.js'))) ? h.ciJob('gate', 'registry:check') : h.FAIL('the installed package does not check the loader: take a version that does')) },\n ]),\n // What loads into every Claude session (UIG-21): CLAUDE.md is an index, the\n // rest loads where it applies, and a rule a check can hold is a check.\n ticket('UIG-21', [\n { what: 'CLAUDE.md with its imports is under 200 lines, and every rule file is scoped', run: () => h.instructions(200) },\n { what: \"CI's job gate runs the token lint\", run: () => {\n const tokens = h.ciJob('gate', 'lint:tokens')\n return tokens.result === 'pass' ? tokens : h.ciJob('gate', 'lint')\n } },\n { what: \"a plain tailwind-merge is an error naming the package's cn()\", run: tokenProbe(\"import { twMerge } from 'tailwind-merge'\\nexport const merged = twMerge('p-2', 'p-3')\\n\", 'error', 'cn()', 'src/lib/__gates_probe__.ts') },\n ]),\n ticket('UIG-22', [\n // The Folders pane's own row, at the commit before peek 3dc663b.\n { what: `a hand-made column header is an error naming ${headers.join(' or ')}`, run: all(h, headers.map((name) => probe(component('<div className=\"flex h-full flex-col\"><div className=\"flex items-center gap-2 px-3 py-2\"><span>Folders</span></div><div /></div>'), 'error', name)), `a hand-made column header is an error naming ${headers.join(' or ')}`) },\n // 26 September: the package's header on its own is a panel made by hand, whose box forgot to scroll.\n { what: \"the package's ContainerHeader on its own is an error naming Panel\", run: probe(`import { ContainerHeader } from '@estiva-app/ui'\\n${component('<div className=\"flex flex-col\"><ContainerHeader title=\"Details\" /><div className=\"px-4\">x</div></div>')}`, 'error', '`Panel`') },\n { what: 'a group heading inside a list is left alone', run: probe(component('<section className=\"flex flex-col gap-px\"><h3 className=\"flex h-8 items-center gap-2 px-3\"><span>Todo</span></h3><div /></section>'), 'none') },\n ]),\n ticket('UIG-23', [\n { what: 'a hand-made empty line is an error naming EmptyState', run: probe(\"export function Probe({ items }: { items: string[] }) {\\n return <div>{items.length === 0 ? <p className=\\\"text-text-secondary\\\">Nothing here</p> : items.map((i) => <span key={i}>{i}</span>)}</div>\\n}\\n\", 'error', 'EmptyState') },\n ]),\n ticket('UIG-24', [\n { what: 'a title= is an error naming WithTooltip', run: probe(`import { Button } from '@estiva-app/ui'\\n${component('<Button title=\"Delete\">x</Button>')}`, 'error', 'WithTooltip') },\n ]),\n ticket('UIG-25', [\n { what: \"SectionLabel's class list, typed by hand, is a warning\", run: probe(component('<span className=\"text-h5 leading-3 signal:font-mono signal:text-small signal:uppercase signal:tracking-widest text-text-secondary\">Label</span>'), 'warning', 'SectionLabel') },\n ]),\n ticket('UIG-27', [\n { what: 'the installed package has Link', run: () => h.contains(installed, /\\bLink\\b/, 'the installed @estiva-app/ui exports Link') },\n { what: 'the installed package has ProgressBar', run: () => h.contains(installed, /\\bProgressBar\\b/, 'the installed @estiva-app/ui exports ProgressBar') },\n ]),\n ticket('UIG-28', [\n { what: 'text-[14px] is an error', run: tokenProbe(component('<div className=\"text-[14px]\">x</div>'), 'error') },\n { what: 'a colour in an inline style is an error', run: tokenProbe(component(\"<div style={{ backgroundColor: '#ff0000' }}>x</div>\"), 'error') },\n { what: 'h-[240px] is a warning', run: tokenProbe(component('<div className=\"h-[240px]\">x</div>'), 'warning') },\n { what: 'h-[240px] is not an error', run: tokenProbe(component('<div className=\"h-[240px]\">x</div>'), 'none') },\n { what: 'a hand-written line height behind an arbitrary variant is an error', run: tokenProbe(component('<div className=\"[&_p]:leading-[1.4]\">x</div>'), 'error') },\n { what: 'a raw colour in a class is an error (the pattern that matched nothing, R1)', run: tokenProbe(component('<div className=\"bg-[#5c69dc]\">x</div>'), 'error', 'raw colour') },\n { what: 'a width and height from a prop pass', run: tokenProbe(\"export function Probe({ size }: { size: number }) {\\n return <div style={{ width: size, height: size }}>x</div>\\n}\\n\", 'none') },\n { what: 'a test file is not checked for hand-written values, while source is', run: all(h, tokenConfigs.map((config) => async () => {\n const code = component(\"<div className=\\\"text-[14px]\\\" style={{ color: 'red' }}>x</div>\")\n const source = await probe(code, 'error', undefined, PROBE, config)()\n if (source.result !== 'pass') return h.FAIL(`source is not checked yet, so this proves nothing: ${source.detail}`)\n return probe(code, 'none', undefined, 'src/components/__gates_probe__.test.tsx', config)()\n }), 'a test file is not checked for hand-written values, while source is') },\n ]),\n // Built 28 September: both apps drew the protocol's text tree by hand, and drifted apart.\n // A file that reads the protocol and draws a quote or a numbered list itself is a third copy.\n ticket('UIG-30', [\n { what: 'the installed package has RichText', run: () => h.contains(installed, /\\bRichText\\b/, 'the installed @estiva-app/ui exports RichText') },\n { what: \"no file draws the protocol's text tree by hand\", run: () => {\n const sources = h.listFiles(at('src'), (n) => /\\.tsx$/.test(n) && !/\\.(test|stories)\\.tsx$/.test(n))\n const own = sources.filter((f) => { const text = h.read(f); return /from ['\"]@estiva-app\\/protocol['\"]/.test(text) && /<(?:blockquote|ol)\\b/.test(text) })\n return own.length ? h.FAIL(`draws the text tree itself (${own.join(', ')}): pass the tree to RichText`) : h.PASS('formatted text is drawn by RichText only')\n } },\n ]),\n // UIG-33, Katerina's picks of 28 September: a row that is one link is `Link` with `cover`, and a\n // chip that is a link is `Chip` with `href`. Before, both apps stretched a link over its row by\n // hand (`after:absolute`) and rounded a Link round a chip, each under an escape.\n ticket('UIG-33', [\n { what: 'the installed package has Link cover and Chip href', run: all(h, [\n () => h.contains(at('node_modules/@estiva-app/ui/dist/Link.d.ts'), /\\bcover\\?: boolean/, 'the installed Link takes cover'),\n () => h.contains(at('node_modules/@estiva-app/ui/dist/Chip.d.ts'), /\\bhref\\?: string/, 'the installed Chip takes href'),\n ], 'the installed Link takes cover and Chip takes href') },\n { what: 'no link is stretched over its row by hand', run: () => {\n const sources = h.listFiles(at('src'), (n) => /\\.tsx$/.test(n) && !/\\.(test|stories)\\.tsx$/.test(n))\n const own = sources.filter((f) => /<(?:Link|a)\\b[^>]*\\bafter:(?:absolute|inset-0)\\b/.test(h.read(f)))\n return own.length ? h.FAIL(`stretches a link by hand (${own.join(', ')}): give the Link cover`) : h.PASS('every whole-row link is Link with cover')\n } },\n ]),\n // Katerina's ruling of 17 September (docs/GATES.md \u00A723): one copy, in the package. An app made\n // by create-estiva-app passes this from its first commit; Peek and Ship do once UIG-32 lands.\n ticket('UIG-32', [\n { what: 'the gate pieces come from the package, and no repo keeps a copy', run: all(h, [\n () => h.contains(at('eslint.gates.config.js'), /from '@estiva-app\\/ui\\/gates'/, 'the gate config imports @estiva-app/ui/gates'),\n () => h.hook('.claude/settings.json', '@estiva-app/ui/dist/gates/cli.js'),\n // `estiva-gates status` where npm can find the command; by path where it cannot,\n // because the install is in the app's folder and the script is in the repo's top one (Ship).\n () => h.contains('package.json', /\"gates:status\":\\s*\"[^\"]*(?:estiva-gates|@estiva-app\\/ui\\/dist\\/gates\\/cli\\.js)[^\"]*status/, \"gates:status runs the package's engine\"),\n () => (h.exists('scripts/gates-status.mjs') ? h.FAIL('scripts/gates-status.mjs is a copy of the status engine: run estiva-gates status instead') : h.PASS('no copy of the status engine')),\n ], 'the gate config, the hook and gates:status are the package\\'s; no copy of the engine') },\n ]),\n ticket('UIG-37', [\n { what: 'a TypeScript eslint-disable comment breaks neither the gate nor the token lint', run: all(h, [\n probe(typescriptDirective, 'none'),\n tokenProbe(typescriptDirective, 'none'),\n ], `${[GATES, ...tokenConfigs].join(', ')}: a TypeScript directive is no error`) },\n { what: 'a token lint on its own is the package\\'s tokenConfig, not built by hand', run: () =>\n (h.exists(at('eslint.tokens.config.js'))\n ? h.contains(at('eslint.tokens.config.js'), /\\btokenConfig\\(/, 'eslint.tokens.config.js runs tokenConfig from @estiva-app/ui/gates')\n : h.PASS('no token lint on its own')) },\n ]),\n ]\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmBA,OAAO,uBAAuB;AAC9B,SAAS,UAAU,wBAAwB;AAC3C,OAAO,YAAY;AAuBZ,SAAS,SAAS,EAAE,WAAW,MAAM,IAAyC,CAAC,GAAkB;AACtG,SAAO;AAAA,IACL,GAAI,aAAa,YAAY,OAAO,QAAQ,UAAU,OAAO,QAAQ;AAAA;AAAA;AAAA,IAGrE,OAAO,aAAa,YAAY,CAAC,cAAc,IAAI,CAAC,0CAA0C;AAAA,IAC9F,SAAS,CAAC,aAAa;AAAA,IACvB,iBAAiB;AAAA,MACf,QAAQ;AAAA,MACR,eAAe,EAAE,cAAc,EAAE,KAAK,KAAK,EAAE;AAAA,IAC/C;AAAA,EACF;AACF;AAOO,SAAS,WAAW,EAAE,WAAW,OAAO,QAAQ,CAAC,GAAG,UAAU,CAAC,EAAE,IAAuB,CAAC,GAAoB;AAClH,SAAO;AAAA,IACL,EAAE,SAAS,CAAC,GAAG,oBAAoB,GAAG,OAAO,EAAE;AAAA,IAC/C;AAAA,MACE,SAAS;AAAA,QACP,GAAG;AAAA,QACH,GAAG;AAAA,QACH,sBAAsB;AAAA,QACtB,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,QACjB,eAAe;AAAA,MACjB;AAAA;AAAA;AAAA;AAAA,MAIA,eAAe,EAAE,+BAA+B,MAAM;AAAA,IACxD;AAAA,IACA,SAAS,EAAE,SAAS,CAAC;AAAA,EACvB;AACF;;;AC7CO,IAAM,oBAA4C;AAAA,EACvD,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AACZ;AAGA,SAAS,IAAI,GAAgB,OAAqD,OAA2C;AAC3H,SAAO,YAAY;AACjB,eAAW,QAAQ,OAAO;AACxB,YAAM,IAAI,MAAM,KAAK;AACrB,UAAI,EAAE,WAAW,OAAQ,QAAO;AAAA,IAClC;AACA,WAAO,EAAE,KAAK,KAAK;AAAA,EACrB;AACF;AAEO,SAAS,UAAU,GAAgB,EAAE,MAAM,KAAK,MAAM,QAAQ,EAAE,KAAK,SAAS,OAAO,MAAM,GAAG,eAAe,CAAC,2BAA2B,kBAAkB,GAAG,UAAU,CAAC,OAAO,EAAE,GAAkC;AACzN,QAAM,MAAM,QAAQ,MAAM,KAAK,GAAG,IAAI,QAAQ,OAAO,EAAE,CAAC;AACxD,QAAM,MAAM,QAAQ,MAAM,SAAY;AACtC,QAAM,QAAQ;AACd,QAAM,QAAQ;AACd,QAAM,KAAK,CAAC,QAAgB,GAAG,GAAG,GAAG,GAAG;AACxC,QAAM,QAAQ,CAAC,MAAc,QAAsC,UAAmB,OAAO,OAAO,SAAS,UAAU,MACrH,EAAE,KAAK,EAAE,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC,GAAI,QAAQ,MAAM,MAAM,QAAQ,SAAS,CAAC;AAC1E,QAAM,YAAY,CAAC,SAAiB;AAAA,WAAuC,IAAI;AAAA;AAAA;AAC/E,QAAM,YAAY,GAAG,6CAA6C;AAClE,QAAM,WAAW,MAA4B,EAAE,KAAK,EAAE,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC,GAAI,QAAQ,OAAO,MAAM,MAAM,MAAM,EAAE,KAAK,GAAG,IAAI,CAAC,GAAG,QAAQ,OAAO,CAAC;AAClJ,QAAM,SAAS,UAAU,kCAAkC;AAE3D,QAAM,sBAAsB;AAC5B,QAAM,SAAS,CAAC,KAAa,QAAqB,QAAQ,WAAuB,EAAE,KAAK,OAAO,kBAAkB,GAAG,GAAG,OAAO,OAAO;AAErI,QAAM,aAAa,CAAC,MAAc,QAAsC,UAAmB,OAAO,UAChG,IAAI,GAAG,aAAa,IAAI,CAAC,WAAW,MAAM,MAAM,QAAQ,UAAU,MAAM,MAAM,CAAC,GAAG,GAAG,aAAa,KAAK,OAAO,CAAC,eAAe;AAEhI,SAAO;AAAA,IACL,OAAO,SAAS;AAAA,MACd,EAAE,MAAM,iCAAiC,KAAK,MAAM,EAAE,OAAO,gBAAgB,cAAc,EAAE;AAAA,IAC/F,CAAC;AAAA,IACD,OAAO,MAAM,KAAK;AAAA,MAChB,EAAE,MAAM,8BAA8B,KAAK,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC,EAAE;AAAA,MACpE,EAAE,MAAM,GAAG,GAAG,cAAc,CAAC,mBAAmB,KAAK,MAAM,EAAE,OAAO,GAAG,cAAc,GAAG,YAAY,EAAE;AAAA,MACtG,EAAE,MAAM,sBAAsB,KAAK,MAAM,EAAE,GAAG,YAAY,EAAE;AAAA,MAC5D,EAAE,MAAM,yCAAyC,KAAK,MAAM,EAAE,KAAK,yBAAyB,OAAO,EAAE;AAAA,MACrG,EAAE,MAAM,6CAA6C,KAAK,MAAM,EAAE,KAAK,EAAE,OAAO,mBAAmB,IAAI,sBAAsB,GAAG,mBAAmB,CAAC,EAAE;AAAA,MACtJ,EAAE,MAAM,4CAA4C,KAAK,MAAM,QAAQ,SAAS,QAAQ,EAAE;AAAA,MAC1F,EAAE,MAAM,uBAAuB,KAAK,MAAM,QAAQ,SAAS,UAAU,yCAAyC,EAAE;AAAA,MAChH,EAAE,MAAM,+CAA+C,KAAK,YAAY;AACtE,cAAM,SAAS,MAAM,MAAM,QAAQ,SAAS,QAAQ,EAAE;AACtD,YAAI,OAAO,WAAW,OAAQ,QAAO,EAAE,KAAK,sDAAsD,OAAO,MAAM,EAAE;AACjH,eAAO,MAAM,QAAQ,QAAQ,QAAW,yCAAyC,EAAE;AAAA,MACrF,EAAE;AAAA,MACF,EAAE,MAAM,wBAAwB,KAAK,MAAM,EAAE,KAAK,oBAAoB,EAAE;AAAA,IAC1E,GAAG,MAAM,KAAK;AAAA,IACd,OAAO,SAAS;AAAA,MACd,EAAE,MAAM,qDAAqD,KAAK,MAAM,EAAE,gBAAgB,QAAQ,EAAE;AAAA,MACpG,EAAE,MAAM,iCAAiC,KAAK,MAAM,EAAE,MAAM,QAAQ,YAAY,EAAE;AAAA,IACpF,CAAC;AAAA,IACD,OAAO,SAAS;AAAA,MACd,EAAE,MAAM,8CAA8C,KAAK,MAAM,UAAU,WAAW,GAAG,SAAS,WAAW,EAAE;AAAA,MAC/G,EAAE,MAAM,oDAAoD,KAAK,MAAM,UAAU,gDAAgD,GAAG,SAAS,UAAU,EAAE;AAAA,MACzJ,EAAE,MAAM,qCAAqC,KAAK,IAAI,GAAG,CAAC,MAAM,UAAU,8BAA8B,GAAG,SAAS,MAAM,GAAG,MAAM,UAAU,8BAA8B,GAAG,SAAS,MAAM,CAAC,GAAG,mCAAmC,EAAE;AAAA,MACtO,EAAE,MAAM,gFAAgF,KAAK,YAAY;AACvG,cAAM,OAAO,MAAM,MAAM,UAAU,UAAU,GAAG,SAAS,QAAQ,EAAE;AACnE,YAAI,KAAK,WAAW,OAAQ,QAAO;AACnC,eAAO,MAAM,UAAU,uBAAuB,GAAG,SAAS,YAAY,EAAE;AAAA,MAC1E,EAAE;AAAA,MACF,EAAE,MAAM,2FAA2F,KAAK,YAAY;AAClH,mBAAW,CAAC,SAAS,IAAI,KAAK,CAAC,CAAC,YAAY,UAAU,GAAG,CAAC,UAAU,QAAQ,GAAG,CAAC,UAAU,aAAa,GAAG,CAAC,SAAS,OAAO,CAAC,GAAG;AAC7H,gBAAM,MAAM,MAAM,MAAM,UAAU,IAAI,OAAO,KAAK,GAAG,SAAS,KAAK,IAAI,IAAI,EAAE;AAC7E,cAAI,IAAI,WAAW,OAAQ,QAAO;AAAA,QACpC;AACA,eAAO,EAAE,KAAK,2CAA2C;AAAA,MAC3D,EAAE;AAAA,MACF,EAAE,MAAM,+EAA+E,KAAK,MAAM,UAAU,wBAAwB,GAAG,SAAS,cAAc,EAAE;AAAA,MAChK,EAAE,MAAM,iEAAiE,KAAK,YAAY;AACxF,cAAM,MAAM,MAAM,MAAM,UAAU,UAAU,GAAG,OAAO,EAAE;AACxD,YAAI,IAAI,WAAW,OAAQ,QAAO,EAAE,KAAK,2DAA2D,IAAI,MAAM,EAAE;AAChH,eAAO,MAAM,mJAAmJ,MAAM,EAAE;AAAA,MAC1K,EAAE;AAAA,MACF,EAAE,MAAM,sEAAsE,KAAK,SAAS;AAAA,IAC9F,CAAC;AAAA,IACD,OAAO,SAAS;AAAA,MACd,EAAE,MAAM,4DAA4D,KAAK,MAAM,oFAAoF,SAAS,WAAW,EAAE;AAAA,MACzL,EAAE,MAAM,sDAAsD,KAAK,MAAM,oHAAoH,SAAS,eAAe,EAAE;AAAA,MACvN,EAAE,MAAM,mEAAmE,KAAK,MAAM,mFAAmF,SAAS,iBAAiB,4BAA4B,EAAE;AAAA,MACjO,EAAE,MAAM,4DAA4D,KAAK,YAAY;AACnF,cAAM,SAAS,MAAM,MAAM,0EAA0E,SAAS,UAAU,4BAA4B,EAAE;AACtJ,YAAI,OAAO,WAAW,OAAQ,QAAO;AACrC,eAAO,MAAM,sEAAsE,QAAQ,QAAW,4BAA4B,EAAE;AAAA,MACtI,EAAE;AAAA,MACF,EAAE,MAAM,0DAA0D,KAAK,MAAM,UAAU,uBAAuB,GAAG,SAAS,UAAU,EAAE;AAAA,MACtI,EAAE,MAAM,2DAA2D,KAAK,YAAY;AAClF,cAAM,OAAO,MAAM,MAAM,UAAU,sBAAsB,GAAG,SAAS,UAAU,EAAE;AACjF,YAAI,KAAK,WAAW,OAAQ,QAAO,EAAE,KAAK,2DAA2D,KAAK,MAAM,EAAE;AAClH,eAAO,MAAM,UAAU,uBAAuB,GAAG,MAAM,EAAE;AAAA,MAC3D,EAAE;AAAA,MACF,EAAE,MAAM,iDAAiD,KAAK,MAAM,UAAU,0CAA0C,GAAG,SAAS,cAAc,EAAE;AAAA,MACpJ,EAAE,MAAM,mEAAmE,KAAK,YAAY;AAC1F,cAAM,MAAM,MAAM,MAAM,uFAAuF,SAAS,QAAW,4BAA4B,EAAE;AACjK,YAAI,IAAI,WAAW,OAAQ,QAAO,EAAE,KAAK,8DAA8D,IAAI,MAAM,EAAE;AACnH,eAAO,MAAM,8KAA8K,QAAQ,QAAW,4BAA4B,EAAE;AAAA,MAC9O,EAAE;AAAA,MACF,EAAE,MAAM,6BAA6B,KAAK,SAAS;AAAA,IACrD,CAAC;AAAA,IACD,OAAO,SAAS;AAAA,MACd,EAAE,MAAM,yDAAyD,KAAK,MAAM;AAAA,EAA4C,UAAU,uCAAuC,CAAC,IAAI,SAAS,kCAAkC,EAAE;AAAA,MAC3N,EAAE,MAAM,uCAAuC,KAAK,MAAM;AAAA,EAA4C,UAAU,4DAA4D,CAAC,IAAI,MAAM,EAAE;AAAA,MACzL,EAAE,MAAM,2DAA2D,KAAK,YAAY;AAClF,cAAM,MAAM,MAAM,MAAM;AAAA,EAA0C,UAAU,mEAAmE,CAAC,IAAI,SAAS,QAAQ,EAAE;AACvK,YAAI,IAAI,WAAW,OAAQ,QAAO,EAAE,KAAK,8DAA8D,IAAI,MAAM,EAAE;AACnH,eAAO,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAAsP,MAAM,EAAE;AAAA,MAC7Q,EAAE;AAAA,MACF,EAAE,MAAM,6BAA6B,KAAK,SAAS;AAAA,IACrD,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,IAKD,OAAO,UAAU;AAAA,MACf,EAAE,MAAM,mFAAmF,KAAK,MAAM;AACpG,cAAM,UAAU,EAAE,UAAU,GAAG,KAAK,GAAG,CAAC,MAAM,UAAU,KAAK,CAAC,KAAK,CAAC,0BAA0B,KAAK,CAAC,CAAC;AACrG,cAAM,MAAM,QAAQ,OAAO,CAAC,MAAM,6DAA6D,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AAC9G,YAAI,IAAI,OAAQ,QAAO,EAAE,KAAK,8BAA8B,IAAI,KAAK,IAAI,CAAC,kDAAkD;AAC5H,cAAM,UAAU,QAAQ,OAAO,CAAC,MAAM,kEAAkE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AACvH,YAAI,QAAQ,WAAW,EAAG,QAAO,EAAE,KAAK,QAAQ,SAAS,GAAG,QAAQ,MAAM,oBAAoB,QAAQ,KAAK,IAAI,CAAC,uBAAuB,uEAAuE;AAC9M,eAAO,EAAE,KAAK,mCAAmC,QAAQ,CAAC,CAAC,EAAE;AAAA,MAC/D,EAAE;AAAA,IACJ,CAAC;AAAA;AAAA;AAAA,IAGD,OAAO,UAAU;AAAA,MACf,EAAE,MAAM,0EAA0E,KAAK,MAAM,EAAE,SAAS,GAAG,cAAc,GAAG,kCAAkC,2CAA2C,EAAE;AAAA,MAC3M,EAAE,MAAM,iDAAiD,KAAK,MAAM,EAAE,SAAS,GAAG,cAAc,GAAG,0CAA0C,qCAAqC,EAAE;AAAA,MACpL,EAAE,MAAM,oDAAoD,KAAK,MAAM,EAAE,MAAM,QAAQ,gBAAgB,EAAE;AAAA;AAAA,MAEzG,EAAE,MAAM,+DAA+D,KAAK,MAAM,EAAE,SAAS,GAAG,YAAY,GAAG,2BAA2B,GAAG,GAAG,YAAY,CAAC,wBAAwB,EAAE;AAAA,MACvL,EAAE,MAAM,uEAAuE,KAAK,MAAM,EAAE,UAAU,GAAG,GAAG,CAAC,EAAE;AAAA,IACjH,CAAC;AAAA,IACD,OAAO,UAAU;AAAA;AAAA;AAAA,MAGf,EAAE,MAAM,4EAA4E,KAAK,MAAM,EAAE,SAAS,GAAG,kDAAkD,GAAG,2BAA2B,iDAAiD,EAAE;AAAA,MAChP,EAAE,MAAM,yBAAyB,KAAK,MAAM,EAAE,MAAM,QAAQ,gBAAgB,EAAE;AAAA,MAC9E,EAAE,MAAM,2DAA2D,KAAK,MAAM,EAAE,OAAO,GAAG,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,0BAA0B,GAAG,cAAc,wCAAwC,IAAI,EAAE,KAAK,iCAAiC,EAAE;AAAA,IACvQ,CAAC;AAAA;AAAA;AAAA,IAGD,OAAO,UAAU;AAAA,MACf,EAAE,MAAM,yDAAyD,KAAK,MAAM,EAAE,SAAS,qCAAqC,kCAAkC,GAAG,gDAAgD,CAAC,OAAO,2CAAsC,EAAE;AAAA,MACjQ,EAAE,MAAM,oCAAoC,KAAK,MAAM,EAAE,SAAS,yBAAyB,2BAA2B,8CAA8C,EAAE;AAAA,MACtK,EAAE,MAAM,6DAA6D,KAAK,MAAM,EAAE,MAAM,GAAG,cAAc,GAAG,0BAA0B,kDAAkD,EAAE;AAAA,MAC1L,GAAI,MAAM,CAAC,EAAE,MAAM,wCAAwC,KAAK,MAAM,EAAE,SAAS,gBAAgB,cAAc,4BAA4B,EAAE,CAAC,IAAI,CAAC;AAAA,MACnJ,EAAE,MAAM,+CAA+C,KAAK,MAAO,EAAE,OAAO,GAAG,kDAAkD,CAAC,KAAK,gBAAgB,KAAK,EAAE,KAAK,GAAG,kDAAkD,CAAC,CAAC,IAAI,EAAE,MAAM,QAAQ,gBAAgB,IAAI,EAAE,KAAK,2EAA2E,EAAG;AAAA,IACzV,CAAC;AAAA;AAAA;AAAA,IAGD,OAAO,UAAU;AAAA,MACf,EAAE,MAAM,gFAAgF,KAAK,MAAM,EAAE,aAAa,GAAG,EAAE;AAAA,MACvH,EAAE,MAAM,qCAAqC,KAAK,MAAM;AACtD,cAAM,SAAS,EAAE,MAAM,QAAQ,aAAa;AAC5C,eAAO,OAAO,WAAW,SAAS,SAAS,EAAE,MAAM,QAAQ,MAAM;AAAA,MACnE,EAAE;AAAA,MACF,EAAE,MAAM,gEAAgE,KAAK,WAAW,2FAA2F,SAAS,QAAQ,4BAA4B,EAAE;AAAA,IACpO,CAAC;AAAA,IACD,OAAO,UAAU;AAAA;AAAA,MAEf,EAAE,MAAM,gDAAgD,QAAQ,KAAK,MAAM,CAAC,IAAI,KAAK,IAAI,GAAG,QAAQ,IAAI,CAAC,SAAS,MAAM,UAAU,kIAAkI,GAAG,SAAS,IAAI,CAAC,GAAG,gDAAgD,QAAQ,KAAK,MAAM,CAAC,EAAE,EAAE;AAAA;AAAA,MAEhW,EAAE,MAAM,qEAAqE,KAAK,MAAM;AAAA,EAAqD,UAAU,uGAAuG,CAAC,IAAI,SAAS,SAAS,EAAE;AAAA,MACvR,EAAE,MAAM,+CAA+C,KAAK,MAAM,UAAU,oIAAoI,GAAG,MAAM,EAAE;AAAA,IAC7N,CAAC;AAAA,IACD,OAAO,UAAU;AAAA,MACf,EAAE,MAAM,wDAAwD,KAAK,MAAM,6MAA+M,SAAS,YAAY,EAAE;AAAA,IACnT,CAAC;AAAA,IACD,OAAO,UAAU;AAAA,MACf,EAAE,MAAM,2CAA2C,KAAK,MAAM;AAAA,EAA4C,UAAU,mCAAmC,CAAC,IAAI,SAAS,aAAa,EAAE;AAAA,IACtL,CAAC;AAAA,IACD,OAAO,UAAU;AAAA,MACf,EAAE,MAAM,0DAA0D,KAAK,MAAM,UAAU,iJAAiJ,GAAG,WAAW,cAAc,EAAE;AAAA,IACxQ,CAAC;AAAA,IACD,OAAO,UAAU;AAAA,MACf,EAAE,MAAM,kCAAkC,KAAK,MAAM,EAAE,SAAS,WAAW,YAAY,2CAA2C,EAAE;AAAA,MACpI,EAAE,MAAM,yCAAyC,KAAK,MAAM,EAAE,SAAS,WAAW,mBAAmB,kDAAkD,EAAE;AAAA,IAC3J,CAAC;AAAA,IACD,OAAO,UAAU;AAAA,MACf,EAAE,MAAM,2BAA2B,KAAK,WAAW,UAAU,sCAAsC,GAAG,OAAO,EAAE;AAAA,MAC/G,EAAE,MAAM,2CAA2C,KAAK,WAAW,UAAU,qDAAqD,GAAG,OAAO,EAAE;AAAA,MAC9I,EAAE,MAAM,0BAA0B,KAAK,WAAW,UAAU,oCAAoC,GAAG,SAAS,EAAE;AAAA,MAC9G,EAAE,MAAM,6BAA6B,KAAK,WAAW,UAAU,oCAAoC,GAAG,MAAM,EAAE;AAAA,MAC9G,EAAE,MAAM,sEAAsE,KAAK,WAAW,UAAU,8CAA8C,GAAG,OAAO,EAAE;AAAA,MAClK,EAAE,MAAM,8EAA8E,KAAK,WAAW,UAAU,uCAAuC,GAAG,SAAS,YAAY,EAAE;AAAA,MACjL,EAAE,MAAM,uCAAuC,KAAK,WAAW,yHAAyH,MAAM,EAAE;AAAA,MAChM,EAAE,MAAM,uEAAuE,KAAK,IAAI,GAAG,aAAa,IAAI,CAAC,WAAW,YAAY;AAClI,cAAM,OAAO,UAAU,+DAAiE;AACxF,cAAM,SAAS,MAAM,MAAM,MAAM,SAAS,QAAW,OAAO,MAAM,EAAE;AACpE,YAAI,OAAO,WAAW,OAAQ,QAAO,EAAE,KAAK,sDAAsD,OAAO,MAAM,EAAE;AACjH,eAAO,MAAM,MAAM,QAAQ,QAAW,2CAA2C,MAAM,EAAE;AAAA,MAC3F,CAAC,GAAG,qEAAqE,EAAE;AAAA,IAC7E,CAAC;AAAA;AAAA;AAAA,IAGD,OAAO,UAAU;AAAA,MACf,EAAE,MAAM,sCAAsC,KAAK,MAAM,EAAE,SAAS,WAAW,gBAAgB,+CAA+C,EAAE;AAAA,MAChJ,EAAE,MAAM,kDAAkD,KAAK,MAAM;AACnE,cAAM,UAAU,EAAE,UAAU,GAAG,KAAK,GAAG,CAAC,MAAM,SAAS,KAAK,CAAC,KAAK,CAAC,yBAAyB,KAAK,CAAC,CAAC;AACnG,cAAM,MAAM,QAAQ,OAAO,CAAC,MAAM;AAAE,gBAAM,OAAO,EAAE,KAAK,CAAC;AAAG,iBAAO,qCAAqC,KAAK,IAAI,KAAK,uBAAuB,KAAK,IAAI;AAAA,QAAE,CAAC;AACzJ,eAAO,IAAI,SAAS,EAAE,KAAK,+BAA+B,IAAI,KAAK,IAAI,CAAC,8BAA8B,IAAI,EAAE,KAAK,0CAA0C;AAAA,MAC7J,EAAE;AAAA,IACJ,CAAC;AAAA;AAAA;AAAA;AAAA,IAID,OAAO,UAAU;AAAA,MACf,EAAE,MAAM,sDAAsD,KAAK,IAAI,GAAG;AAAA,QACxE,MAAM,EAAE,SAAS,GAAG,4CAA4C,GAAG,sBAAsB,gCAAgC;AAAA,QACzH,MAAM,EAAE,SAAS,GAAG,4CAA4C,GAAG,oBAAoB,+BAA+B;AAAA,MACxH,GAAG,oDAAoD,EAAE;AAAA,MACzD,EAAE,MAAM,6CAA6C,KAAK,MAAM;AAC9D,cAAM,UAAU,EAAE,UAAU,GAAG,KAAK,GAAG,CAAC,MAAM,SAAS,KAAK,CAAC,KAAK,CAAC,yBAAyB,KAAK,CAAC,CAAC;AACnG,cAAM,MAAM,QAAQ,OAAO,CAAC,MAAM,mDAAmD,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AACpG,eAAO,IAAI,SAAS,EAAE,KAAK,6BAA6B,IAAI,KAAK,IAAI,CAAC,wBAAwB,IAAI,EAAE,KAAK,yCAAyC;AAAA,MACpJ,EAAE;AAAA,IACJ,CAAC;AAAA;AAAA;AAAA,IAGD,OAAO,UAAU;AAAA,MACf,EAAE,MAAM,mEAAmE,KAAK,IAAI,GAAG;AAAA,QACrF,MAAM,EAAE,SAAS,GAAG,wBAAwB,GAAG,iCAAiC,8CAA8C;AAAA,QAC9H,MAAM,EAAE,KAAK,yBAAyB,kCAAkC;AAAA;AAAA;AAAA,QAGxE,MAAM,EAAE,SAAS,gBAAgB,6FAA6F,wCAAwC;AAAA,QACtK,MAAO,EAAE,OAAO,0BAA0B,IAAI,EAAE,KAAK,0FAA0F,IAAI,EAAE,KAAK,8BAA8B;AAAA,MAC1L,GAAG,qFAAsF,EAAE;AAAA,IAC7F,CAAC;AAAA,IACD,OAAO,UAAU;AAAA,MACf,EAAE,MAAM,kFAAkF,KAAK,IAAI,GAAG;AAAA,QACpG,MAAM,qBAAqB,MAAM;AAAA,QACjC,WAAW,qBAAqB,MAAM;AAAA,MACxC,GAAG,GAAG,CAAC,OAAO,GAAG,YAAY,EAAE,KAAK,IAAI,CAAC,sCAAsC,EAAE;AAAA,MACjF,EAAE,MAAM,2EAA4E,KAAK,MACtF,EAAE,OAAO,GAAG,yBAAyB,CAAC,IACnC,EAAE,SAAS,GAAG,yBAAyB,GAAG,mBAAmB,oEAAoE,IACjI,EAAE,KAAK,0BAA0B,EAAG;AAAA,IAC5C,CAAC;AAAA,EACH;AACF;",
6
6
  "names": []
7
7
  }
package/dist/index.js CHANGED
@@ -37,19 +37,20 @@ function cn(...inputs) {
37
37
 
38
38
  // src/Link.tsx
39
39
  import { jsx } from "react/jsx-runtime";
40
+ var COVER_CLASSES = "after:absolute after:inset-0 after:rounded-md focus-visible:outline-none focus-visible:after:[outline-style:auto]";
40
41
  var VARIANT_CLASSES = {
41
42
  text: "text-info-default underline underline-offset-2 hover:opacity-80",
42
43
  quiet: "hover:underline",
43
44
  underlined: "underline underline-offset-2 hover:text-text-primary",
44
45
  plain: ""
45
46
  };
46
- function Link({ href, variant = "text", external = false, truncate = false, className, children, ...props }) {
47
+ function Link({ href, variant = "text", external = false, truncate = false, cover = false, className, children, ...props }) {
47
48
  return /* @__PURE__ */ jsx(
48
49
  "a",
49
50
  {
50
51
  href,
51
52
  ...external ? { target: "_blank", rel: "noopener noreferrer" } : {},
52
- className: cn(VARIANT_CLASSES[variant], truncate && "truncate", className),
53
+ className: cn(VARIANT_CLASSES[variant], truncate && "truncate", cover && COVER_CLASSES, className),
53
54
  ...props,
54
55
  children
55
56
  }
@@ -306,15 +307,15 @@ function Kbd({ children, className, tone = "default" }) {
306
307
  }
307
308
 
308
309
  // src/looks.ts
309
- var FIELD_BOX_CLASSES = "bg-bg-field border border-border-field shadow-field hover:border-border-strong focus:border-border-focus rounded-lg";
310
+ var FIELD_BOX_CLASSES = "bg-bg-field border border-border-field shadow-field hover:border-border-strong group-hover/field:border-border-strong focus:border-border-focus group-hover/field:focus:border-border-focus rounded-lg";
310
311
  var FIELD_TEXT_CLASSES = "text-text-primary placeholder:text-text-muted";
311
- var FIELD_DISABLED_CLASSES = "disabled:pointer-events-none disabled:bg-bg-disabled disabled:text-text-disabled";
312
+ var FIELD_DISABLED_CLASSES = "disabled:pointer-events-none disabled:bg-bg-disabled disabled:text-text-disabled disabled:border-border-field group-hover/field:disabled:border-border-field";
312
313
  var FIELD_FOCUS_RING_CLASSES = "signal:transition-shadow signal:focus:shadow-focus-ring";
313
314
  var FIELD_SIZE_CLASSES = {
314
315
  default: "px-3 py-2 text-input-value",
315
316
  small: "h-6 min-h-6 px-2 text-caption"
316
317
  };
317
- var FIELD_SHELL_CLASSES = "bg-bg-field border border-border-field shadow-field hover:border-border-strong rounded-lg transition-colors";
318
+ var FIELD_SHELL_CLASSES = "bg-bg-field border border-border-field shadow-field hover:border-border-strong group-hover/field:border-border-strong rounded-lg transition-colors";
318
319
  var FLOATING_SURFACE_CLASSES = "rounded-lg border border-border-default bg-bg-elevated shadow-lg";
319
320
  var CHIP_TEXT_CLASSES = "text-chip signal:font-mono signal:text-small signal:font-semibold signal:tabular-nums";
320
321
  var BARE_INPUT_CLASSES = "bg-transparent text-text-primary placeholder:text-text-muted outline-none";
@@ -1000,7 +1001,7 @@ function Checkbox({ checked, onChange, disabled: ownDisabled = false, label, row
1000
1001
  }
1001
1002
 
1002
1003
  // src/Chip.tsx
1003
- import { jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
1004
+ import { Fragment as Fragment3, jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
1004
1005
  var typeStyles = {
1005
1006
  neutral: "bg-bg-inset text-text-primary",
1006
1007
  brand: "bg-accent-muted text-accent-primary signal:border signal:border-accent-outline",
@@ -1009,8 +1010,9 @@ var typeStyles = {
1009
1010
  success: "bg-success-muted text-success-default signal:border signal:border-success-outline",
1010
1011
  error: "bg-error-muted text-error-default signal:border signal:border-error-outline"
1011
1012
  };
1012
- function Chip({ type = "neutral", label, leadingIcon, trailingIcon, className }) {
1013
- return /* @__PURE__ */ jsxs11("div", { className: cn("inline-flex min-w-0 items-center justify-center gap-1.5 rounded-full max-h-[20px] px-2 py-1", typeStyles[type], className), children: [
1013
+ function Chip({ type = "neutral", label, leadingIcon, trailingIcon, href, onClick, className }) {
1014
+ const classes = cn("inline-flex min-w-0 items-center justify-center gap-1.5 rounded-full max-h-[20px] px-2 py-1", typeStyles[type], className);
1015
+ const inside = /* @__PURE__ */ jsxs11(Fragment3, { children: [
1014
1016
  leadingIcon && /* @__PURE__ */ jsx17("span", { className: "flex size-3 shrink-0 items-center justify-center", children: leadingIcon }),
1015
1017
  label && // A chip given a `max-w-*` cuts a long label with an ellipsis instead
1016
1018
  // of growing past it — Ship's project chip on an issue row
@@ -1023,6 +1025,8 @@ function Chip({ type = "neutral", label, leadingIcon, trailingIcon, className })
1023
1025
  /* @__PURE__ */ jsx17("span", { className: cn("min-w-0 overflow-x-clip text-ellipsis whitespace-nowrap signal:tracking-wide", CHIP_TEXT_CLASSES), children: label }),
1024
1026
  trailingIcon && /* @__PURE__ */ jsx17("span", { className: "flex size-3 shrink-0 items-center justify-center", children: trailingIcon })
1025
1027
  ] });
1028
+ if (href) return /* @__PURE__ */ jsx17("a", { href, onClick, className: cn("relative", classes), children: inside });
1029
+ return /* @__PURE__ */ jsx17("div", { className: classes, children: inside });
1026
1030
  }
1027
1031
 
1028
1032
  // src/CommandPalette.tsx
@@ -1113,7 +1117,7 @@ function SectionLabel({
1113
1117
  }
1114
1118
 
1115
1119
  // src/Menu.tsx
1116
- import { Fragment as Fragment3, jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
1120
+ import { Fragment as Fragment4, jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
1117
1121
  var MenuContext = createContext4(null);
1118
1122
  function MenuPanel({ children, className, ...props }) {
1119
1123
  return /* @__PURE__ */ jsx20(
@@ -1252,7 +1256,7 @@ function menuItemClassName({ size, selected, className }) {
1252
1256
  }
1253
1257
  function MenuItemBody({ label, children, size = "default", description, leading, trailing, hint, shortcut, submenu, destructive, selected }) {
1254
1258
  const edge = trailing ?? (shortcut ? /* @__PURE__ */ jsx20(Kbd, { children: shortcut }) : submenu ? /* @__PURE__ */ jsx20(IconChevronRight, { size: 16, stroke: 1.5, className: "shrink-0 text-text-muted" }) : null);
1255
- return /* @__PURE__ */ jsxs13(Fragment3, { children: [
1259
+ return /* @__PURE__ */ jsxs13(Fragment4, { children: [
1256
1260
  leading && /* @__PURE__ */ jsx20("span", { className: "flex shrink-0 items-center", children: leading }),
1257
1261
  children ?? /* @__PURE__ */ jsxs13("span", { className: cn("flex min-w-0 flex-1 flex-col", size === "tall" && "gap-[2px]"), children: [
1258
1262
  /* @__PURE__ */ jsx20("span", { className: cn("truncate text-body-2", destructive ? "text-error-default" : "text-text-primary"), children: label }),
@@ -1422,7 +1426,7 @@ function ChipInput({
1422
1426
  filter,
1423
1427
  itemToStringLabel: (option) => option.label,
1424
1428
  children: [
1425
- /* @__PURE__ */ jsxs14(Combobox.Chips, { ref: setBox, className: cn(FIELD_SHELL_CLASSES, "focus-within:border-border-focus focus-within:hover:border-border-focus px-3 py-1.5 flex flex-wrap items-center gap-1.5 min-h-[38px] cursor-text signal:transition-shadow signal:focus-within:shadow-focus-ring"), children: [
1429
+ /* @__PURE__ */ jsxs14(Combobox.Chips, { ref: setBox, className: cn(FIELD_SHELL_CLASSES, "focus-within:border-border-focus focus-within:hover:border-border-focus group-hover/field:focus-within:border-border-focus px-3 py-1.5 flex flex-wrap items-center gap-1.5 min-h-[38px] cursor-text signal:transition-shadow signal:focus-within:shadow-focus-ring"), children: [
1426
1430
  value.map((option) => /* @__PURE__ */ jsxs14(Combobox.Chip, { className: cn(CHIP_BOX, chipPadding(!!chipLeading, true)), children: [
1427
1431
  chipLeading && /* @__PURE__ */ jsx21("span", { className: "flex shrink-0 items-center", children: chipLeading(option) }),
1428
1432
  /* @__PURE__ */ jsx21("span", { className: CHIP_LABEL, children: option.label }),
@@ -1499,16 +1503,21 @@ function Field({ label, required = false, helper, error, children }) {
1499
1503
  const control = required && isValidElement(children) ? cloneElement(children, {
1500
1504
  "aria-required": children.props["aria-required"] ?? true
1501
1505
  }) : children;
1502
- return /* @__PURE__ */ jsxs16(BaseField2.Root, { invalid: !!error, className: "flex flex-col gap-2", children: [
1503
- /* @__PURE__ */ jsxs16(BaseField2.Label, { className: cn("text-input-label text-text-primary", required && "flex items-center"), children: [
1504
- label,
1505
- required && /* @__PURE__ */ jsx23("span", { "aria-hidden": "true", className: "text-error-default ml-0.5", children: "*" })
1506
- ] }),
1507
- /* @__PURE__ */ jsxs16("div", { className: "flex flex-col gap-1.5", children: [
1508
- control,
1509
- error != null ? /* @__PURE__ */ jsx23(BaseField2.Error, { match: true, className: LINE_STYLES.error, children: error }) : helper != null ? /* @__PURE__ */ jsx23(BaseField2.Description, { className: LINE_STYLES.helper, children: helper }) : null
1506
+ return (
1507
+ // `group/field`: the label, the box and the line under it are one thing under the pointer —
1508
+ // anywhere in it strengthens the box's border, as hovering the box does (Katerina, 28 September).
1509
+ // Before, the 8px between the label and the box was a gap where the border went out again.
1510
+ /* @__PURE__ */ jsxs16(BaseField2.Root, { invalid: !!error, className: "group/field flex flex-col gap-2", children: [
1511
+ /* @__PURE__ */ jsxs16(BaseField2.Label, { className: cn("text-input-label text-text-primary", required && "flex items-center"), children: [
1512
+ label,
1513
+ required && /* @__PURE__ */ jsx23("span", { "aria-hidden": "true", className: "text-error-default ml-0.5", children: "*" })
1514
+ ] }),
1515
+ /* @__PURE__ */ jsxs16("div", { className: "flex flex-col gap-1.5", children: [
1516
+ control,
1517
+ error != null ? /* @__PURE__ */ jsx23(BaseField2.Error, { match: true, className: LINE_STYLES.error, children: error }) : helper != null ? /* @__PURE__ */ jsx23(BaseField2.Description, { className: LINE_STYLES.helper, children: helper }) : null
1518
+ ] })
1510
1519
  ] })
1511
- ] });
1520
+ );
1512
1521
  }
1513
1522
  function FieldLine({ tone = "helper", children, className }) {
1514
1523
  return /* @__PURE__ */ jsx23("p", { role: tone === "error" ? "alert" : "status", className: cn(LINE_STYLES[tone], className), children });
@@ -1519,6 +1528,7 @@ import { useLayoutEffect as useLayoutEffect2, useRef as useRef3 } from "react";
1519
1528
  import { Fieldset } from "@base-ui/react/fieldset";
1520
1529
  import { Form as BaseForm } from "@base-ui/react/form";
1521
1530
  import { jsx as jsx24 } from "react/jsx-runtime";
1531
+ var FIELDS_CLASSES = "flex flex-col gap-6";
1522
1532
  var CONTROL = 'input:not([type="hidden"]), textarea, select, button, [role="checkbox"], [role="combobox"], [tabindex]:not([tabindex="-1"])';
1523
1533
  var ONE_LINE = /* @__PURE__ */ new Set(["", "text", "search", "email", "url", "tel", "password", "number"]);
1524
1534
  function usable(element) {
@@ -1532,7 +1542,7 @@ function firstInvalid(form) {
1532
1542
  }
1533
1543
  return void 0;
1534
1544
  }
1535
- function Form({ onSubmit, busy: ownBusy = false, enterSends = true, className, children, ...props }) {
1545
+ function Form({ onSubmit, busy: ownBusy = false, enterSends = true, layout = "fields", className, children, ...props }) {
1536
1546
  const outerBusy = useFormBusy();
1537
1547
  const busy = ownBusy || outerBusy;
1538
1548
  const form = useRef3(null);
@@ -1583,7 +1593,7 @@ function Form({ onSubmit, busy: ownBusy = false, enterSends = true, className, c
1583
1593
  ref: form,
1584
1594
  ...props,
1585
1595
  tabIndex: busy ? -1 : props.tabIndex,
1586
- className: cn("outline-none", className),
1596
+ className: cn("outline-none", layout === "fields" && FIELDS_CLASSES, className),
1587
1597
  onFocus: (event) => {
1588
1598
  if (event.target !== form.current) lastInside.current = event.target;
1589
1599
  props.onFocus?.(event);
@@ -1622,7 +1632,7 @@ function SkeletonList({ rows = 8, className }) {
1622
1632
  }
1623
1633
 
1624
1634
  // src/CommandPalette.tsx
1625
- import { Fragment as Fragment4, jsx as jsx26, jsxs as jsxs18 } from "react/jsx-runtime";
1635
+ import { Fragment as Fragment5, jsx as jsx26, jsxs as jsxs18 } from "react/jsx-runtime";
1626
1636
  var PaletteContext = createContext5(null);
1627
1637
  function usePalette(part) {
1628
1638
  const palette = useContext5(PaletteContext);
@@ -1815,7 +1825,7 @@ function CommandPaletteSearch({ query, onQueryChange, placeholder, groups, chip,
1815
1825
  ) }, row.id)
1816
1826
  ) })
1817
1827
  ] }, group.value) }),
1818
- /* @__PURE__ */ jsx26(Autocomplete.Status, { className: "flex items-center gap-2 px-3 [&:not(:empty)]:h-8", children: pending && /* @__PURE__ */ jsxs18(Fragment4, { children: [
1828
+ /* @__PURE__ */ jsx26(Autocomplete.Status, { className: "flex items-center gap-2 px-3 [&:not(:empty)]:h-8", children: pending && /* @__PURE__ */ jsxs18(Fragment5, { children: [
1819
1829
  /* @__PURE__ */ jsx26(IconLoader22, { size: 12, stroke: 1.5, className: "shrink-0 animate-spin text-text-muted" }),
1820
1830
  /* @__PURE__ */ jsx26("span", { className: "text-caption text-text-muted", children: pending })
1821
1831
  ] }) }),
@@ -1878,8 +1888,8 @@ function CommandPaletteForm({ chip, icon, submitLabel, onSubmit, submitWaits, wo
1878
1888
  icon != null && /* @__PURE__ */ jsx26("span", { className: "flex shrink-0 items-center text-text-secondary", children: icon }),
1879
1889
  /* @__PURE__ */ jsx26(LevelChip, { chip, onBack: back })
1880
1890
  ] }),
1881
- /* @__PURE__ */ jsxs18(Form, { "data-command-palette-fields": "", busy, enterSends: false, onSubmit: submit, className: "flex min-h-0 flex-col", children: [
1882
- /* @__PURE__ */ jsx26(ScrollArea, { viewportClassName: "max-h-[420px]", contentClassName: "flex flex-col px-5 py-4", children: /* @__PURE__ */ jsx26("div", { className: "flex flex-col gap-4", children }) }),
1891
+ /* @__PURE__ */ jsxs18(Form, { "data-command-palette-fields": "", busy, enterSends: false, onSubmit: submit, layout: "free", className: "flex min-h-0 flex-col", children: [
1892
+ /* @__PURE__ */ jsx26(ScrollArea, { viewportClassName: "max-h-[420px]", contentClassName: "flex flex-col px-5 py-4", children: /* @__PURE__ */ jsx26("div", { className: FIELDS_CLASSES, children }) }),
1883
1893
  /* @__PURE__ */ jsxs18("div", { className: "flex shrink-0 items-center gap-3 px-5 pb-4", children: [
1884
1894
  /* @__PURE__ */ jsx26("div", { className: "min-w-0 flex-1", children: working ? /* @__PURE__ */ jsx26(FieldLine, { children: working.line }) : error ? /* @__PURE__ */ jsx26(FieldLine, { tone: "error", children: error }) : null }),
1885
1895
  /* @__PURE__ */ jsx26(
@@ -1922,7 +1932,7 @@ function CommandPaletteQuote({ children }) {
1922
1932
  }
1923
1933
 
1924
1934
  // src/InlineChip.tsx
1925
- import { Fragment as Fragment5, jsx as jsx27, jsxs as jsxs19 } from "react/jsx-runtime";
1935
+ import { Fragment as Fragment6, jsx as jsx27, jsxs as jsxs19 } from "react/jsx-runtime";
1926
1936
  var INLINE_CHIP_CLASSES = "inline-flex h-[1.4em] items-center gap-1 rounded-sm px-1 mx-0.5 align-top text-body-2 font-normal select-none";
1927
1937
  var INLINE_CHIP_TONE_CLASSES = {
1928
1938
  /** A thing or a place — anything that is not a person. */
@@ -1944,7 +1954,7 @@ function inlineChipClassName(tone, className) {
1944
1954
  return cn(INLINE_CHIP_CLASSES, INLINE_CHIP_TONE_CLASSES[tone], className);
1945
1955
  }
1946
1956
  function InlineChip({ tone = "neutral", icon, href, className, children, ...props }) {
1947
- const body = /* @__PURE__ */ jsxs19(Fragment5, { children: [
1957
+ const body = /* @__PURE__ */ jsxs19(Fragment6, { children: [
1948
1958
  icon && /* @__PURE__ */ jsx27("span", { className: "flex size-4 shrink-0 items-center justify-center text-text-secondary", children: icon }),
1949
1959
  children
1950
1960
  ] });
@@ -1956,7 +1966,7 @@ function InlineChip({ tone = "neutral", icon, href, className, children, ...prop
1956
1966
  }
1957
1967
 
1958
1968
  // src/RichText.tsx
1959
- import { Fragment as Fragment6, jsx as jsx28, jsxs as jsxs20 } from "react/jsx-runtime";
1969
+ import { Fragment as Fragment7, jsx as jsx28, jsxs as jsxs20 } from "react/jsx-runtime";
1960
1970
  var RICH_TEXT_CLASSES = {
1961
1971
  base: "flex flex-col gap-1 break-words text-text-secondary",
1962
1972
  /* A heading binds to what follows it: 12px above (8 and the 4px gap), 8px below (4 and the gap); two headings in a row, 12px apart. Level 3 keeps the text's colour, one step lower. */
@@ -2004,7 +2014,7 @@ function textWithLinks(text, renderText) {
2004
2014
  });
2005
2015
  }
2006
2016
  function Runs({ runs, draw }) {
2007
- return /* @__PURE__ */ jsx28(Fragment6, { children: (runs ?? []).map((run, i) => {
2017
+ return /* @__PURE__ */ jsx28(Fragment7, { children: (runs ?? []).map((run, i) => {
2008
2018
  if (run.reference) return /* @__PURE__ */ jsx28("span", { children: draw.renderReference(run.reference, run) }, i);
2009
2019
  if (run.marks.includes("code")) {
2010
2020
  return /* @__PURE__ */ jsx28("code", { children: run.text }, i);
@@ -2159,7 +2169,7 @@ function PersonTrigger({ name, picture, fallback, size, open = false, compact =
2159
2169
  }
2160
2170
 
2161
2171
  // src/IdentityMenu.tsx
2162
- import { Fragment as Fragment7, jsx as jsx31, jsxs as jsxs23 } from "react/jsx-runtime";
2172
+ import { Fragment as Fragment8, jsx as jsx31, jsxs as jsxs23 } from "react/jsx-runtime";
2163
2173
  function IdentityPanel({ className, onClose = () => {
2164
2174
  }, ...rest }) {
2165
2175
  return /* @__PURE__ */ jsx31(MenuPanel, { className: cn("w-72", className), children: /* @__PURE__ */ jsx31(IdentityRows, { ...rest, onClose }) });
@@ -2170,7 +2180,7 @@ function IdentityRows({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, on
2170
2180
  action();
2171
2181
  };
2172
2182
  const appRows = typeof children === "function" ? children(onClose) : children;
2173
- return /* @__PURE__ */ jsxs23(Fragment7, { children: [
2183
+ return /* @__PURE__ */ jsxs23(Fragment8, { children: [
2174
2184
  /* @__PURE__ */ jsxs23(MenuSection, { label: signedIn ? "Signed in as" : "Acting as", children: [
2175
2185
  /* @__PURE__ */ jsx31(MenuRow, { children: /* @__PURE__ */ jsx31("div", { className: "flex min-w-0 text-body-2-strong", children: /* @__PURE__ */ jsx31(Person, { name: me.name, picture: me.picture, fallback: "Anonymous", size: 28 }) }) }),
2176
2186
  /* @__PURE__ */ jsx31(Note, { children: signedIn ? "Your Estiva ID. The same person you are in every Estiva app." : "A key held by this browser only. Nobody else knows who you are." }),
@@ -2179,14 +2189,14 @@ function IdentityRows({ me, signedIn, relayUrl, idBase, onCopyKey, onSignOut, on
2179
2189
  " \xB7 known to Estiva, never published to the relay"
2180
2190
  ] })
2181
2191
  ] }),
2182
- relayUrl && /* @__PURE__ */ jsxs23(Fragment7, { children: [
2192
+ relayUrl && /* @__PURE__ */ jsxs23(Fragment8, { children: [
2183
2193
  /* @__PURE__ */ jsx31(Divider, { className: "mx-0 my-2" }),
2184
2194
  /* @__PURE__ */ jsxs23(MenuSection, { label: "Workspace", children: [
2185
2195
  /* @__PURE__ */ jsx31(MenuRow, { children: /* @__PURE__ */ jsx31("span", { className: "break-all font-mono text-caption text-text-primary", children: relayUrl }) }),
2186
2196
  /* @__PURE__ */ jsx31(Note, { children: "Everyone on this relay shares this workspace, in every app." })
2187
2197
  ] })
2188
2198
  ] }),
2189
- appRows && /* @__PURE__ */ jsxs23(Fragment7, { children: [
2199
+ appRows && /* @__PURE__ */ jsxs23(Fragment8, { children: [
2190
2200
  /* @__PURE__ */ jsx31(Divider, { className: "mx-0 my-2" }),
2191
2201
  appRows
2192
2202
  ] }),
@@ -2302,7 +2312,10 @@ function Select({ value, onChange, options, size = "default", ariaLabel, placeho
2302
2312
  // The focused border survives a hover: hover alone strengthens the
2303
2313
  // hairline, but hover while focused must not grey the focus colour —
2304
2314
  // the stacked variant outranks plain hover by specificity.
2315
+ // Anywhere in the `Field` around it counts as over it (Katerina, 28 September).
2305
2316
  !held && "hover:border-border-strong focus-visible:hover:border-border-focus aria-expanded:hover:border-border-focus",
2317
+ // Left out while focused or open, so their colour stays under the pointer.
2318
+ !held && "group-hover/field:border-border-strong group-hover/field:focus-visible:border-border-focus group-hover/field:aria-expanded:border-border-focus",
2306
2319
  FIELD_DISABLED_CLASSES,
2307
2320
  held && "cursor-not-allowed bg-bg-disabled text-text-disabled",
2308
2321
  "focus-visible:border-border-focus aria-expanded:border-border-focus",
@@ -2522,7 +2535,7 @@ function DialogShell({ title, onClose, headerContent, footer, children, bodyClas
2522
2535
  }
2523
2536
 
2524
2537
  // src/ConfirmDialog.tsx
2525
- import { Fragment as Fragment8, jsx as jsx38, jsxs as jsxs27 } from "react/jsx-runtime";
2538
+ import { Fragment as Fragment9, jsx as jsx38, jsxs as jsxs27 } from "react/jsx-runtime";
2526
2539
  function ConfirmDialog({ title, children, confirmLabel, destructive = false, onConfirm, onClose }) {
2527
2540
  const [busy, setBusy] = useState4(false);
2528
2541
  const confirm = async () => {
@@ -2540,7 +2553,7 @@ function ConfirmDialog({ title, children, confirmLabel, destructive = false, onC
2540
2553
  alert: true,
2541
2554
  title,
2542
2555
  onClose,
2543
- footer: /* @__PURE__ */ jsxs27(Fragment8, { children: [
2556
+ footer: /* @__PURE__ */ jsxs27(Fragment9, { children: [
2544
2557
  /* @__PURE__ */ jsx38(Button, { variant: "muted", onClick: onClose, disabled: busy, children: "Cancel" }),
2545
2558
  /* @__PURE__ */ jsx38(Button, { variant: destructive ? "destructive" : "primary", onClick: () => void confirm(), disabled: busy, children: confirmLabel })
2546
2559
  ] }),
@@ -2712,7 +2725,7 @@ function ProgressBar({ value, max, label, variant = "default", className }) {
2712
2725
  }
2713
2726
 
2714
2727
  // src/Breadcrumb.tsx
2715
- import { Fragment as Fragment9, useCallback as useCallback2, useLayoutEffect as useLayoutEffect4, useRef as useRef8, useState as useState6 } from "react";
2728
+ import { Fragment as Fragment10, useCallback as useCallback2, useLayoutEffect as useLayoutEffect4, useRef as useRef8, useState as useState6 } from "react";
2716
2729
  import { jsx as jsx42, jsxs as jsxs28 } from "react/jsx-runtime";
2717
2730
  function Breadcrumb({ items, className }) {
2718
2731
  const navRef = useRef8(null);
@@ -2756,7 +2769,7 @@ function Breadcrumb({ items, className }) {
2756
2769
  // truncates and is measured.
2757
2770
  /* @__PURE__ */ jsx42("span", { className: cn("flex min-w-0", item.mono && "shrink-0 font-mono text-caption", tone, "hover:text-text-primary"), children: /* @__PURE__ */ jsx42(Link, { ref: setLabelRef, variant: "plain", truncate: true, href: item.href, onClick: item.onClick, children: item.label }) })
2758
2771
  ) : /* @__PURE__ */ jsx42("span", { ref: setLabelRef, className: text, "aria-current": last ? "page" : void 0, children: item.label });
2759
- return /* @__PURE__ */ jsxs28(Fragment9, { children: [
2772
+ return /* @__PURE__ */ jsxs28(Fragment10, { children: [
2760
2773
  index > 0 && /* @__PURE__ */ jsx42("span", { "aria-hidden": "true", className: "shrink-0 text-text-muted", children: "/" }),
2761
2774
  item.icon && /* @__PURE__ */ jsx42("span", { "aria-hidden": "true", className: cn("flex shrink-0", tone), children: item.icon }),
2762
2775
  truncated.has(index) ? (
@@ -3096,7 +3109,7 @@ function SearchInput({ shortcut, className, placeholder = "Search\u2026", ...pro
3096
3109
  // src/SectionHeader.tsx
3097
3110
  import { IconChevronRight as IconChevronRight2 } from "@tabler/icons-react";
3098
3111
  import { useRender } from "@base-ui/react/use-render";
3099
- import { Fragment as Fragment10, jsx as jsx53, jsxs as jsxs36 } from "react/jsx-runtime";
3112
+ import { Fragment as Fragment11, jsx as jsx53, jsxs as jsxs36 } from "react/jsx-runtime";
3100
3113
  function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, trailing, actions, showActions = "hover", hover = "fill", render, className }) {
3101
3114
  const titleElement = useRender({
3102
3115
  render: render ?? (chevron ? /* @__PURE__ */ jsx53("button", { type: "button", onClick: onToggle, "aria-expanded": isExpanded }) : /* @__PURE__ */ jsx53("span", {})),
@@ -3105,7 +3118,7 @@ function SectionHeader({ title, chevron = false, isExpanded = true, onToggle, tr
3105
3118
  // whole row up to the actions is the hit target, as it was when the
3106
3119
  // row itself carried the click.
3107
3120
  className: "flex h-full min-w-0 flex-1 items-center gap-1 text-left",
3108
- children: /* @__PURE__ */ jsxs36(Fragment10, { children: [
3121
+ children: /* @__PURE__ */ jsxs36(Fragment11, { children: [
3109
3122
  chevron && /* @__PURE__ */ jsx53(
3110
3123
  IconChevronRight2,
3111
3124
  {