cursedbelt 5.78.0 → 5.79.1
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.
- package/dist/react/canvas-hud/HudControls.d.ts +8 -2
- package/dist/react/canvas-hud/HudControls.d.ts.map +1 -1
- package/dist/react/canvas-hud/HudControls.js +6 -3
- package/dist/react/canvas-hud/HudControls.js.map +1 -1
- package/dist/react/components/AppShell.d.ts +14 -0
- package/dist/react/components/AppShell.d.ts.map +1 -1
- package/dist/react/components/AppShell.js +7 -31
- package/dist/react/components/AppShell.js.map +1 -1
- package/dist/react/components/ChipFilterStrip.d.ts.map +1 -1
- package/dist/react/components/ChipFilterStrip.js +2 -18
- package/dist/react/components/ChipFilterStrip.js.map +1 -1
- package/dist/react/components/FileDropZone.d.ts.map +1 -1
- package/dist/react/components/FileDropZone.js +4 -1
- package/dist/react/components/FileDropZone.js.map +1 -1
- package/dist/react/components/KanbanBoard.d.ts +55 -0
- package/dist/react/components/KanbanBoard.d.ts.map +1 -0
- package/dist/react/components/KanbanBoard.js +89 -0
- package/dist/react/components/KanbanBoard.js.map +1 -0
- package/dist/react/components/ResponsiveWorkbench.d.ts +14 -1
- package/dist/react/components/ResponsiveWorkbench.d.ts.map +1 -1
- package/dist/react/components/ResponsiveWorkbench.js +33 -4
- package/dist/react/components/ResponsiveWorkbench.js.map +1 -1
- package/dist/react/components/SelectField.d.ts +18 -48
- package/dist/react/components/SelectField.d.ts.map +1 -1
- package/dist/react/components/SelectField.js +88 -11
- package/dist/react/components/SelectField.js.map +1 -1
- package/dist/react/components/SelectFieldImpl.d.ts +7 -0
- package/dist/react/components/SelectFieldImpl.d.ts.map +1 -0
- package/dist/react/components/SelectFieldImpl.js +26 -0
- package/dist/react/components/SelectFieldImpl.js.map +1 -0
- package/dist/react/components/filterChip.d.ts +8 -0
- package/dist/react/components/filterChip.d.ts.map +1 -0
- package/dist/react/components/filterChip.js +20 -0
- package/dist/react/components/filterChip.js.map +1 -0
- package/dist/react/hooks/pointerDnd.d.ts +37 -3
- package/dist/react/hooks/pointerDnd.d.ts.map +1 -1
- package/dist/react/hooks/pointerDnd.js +101 -9
- package/dist/react/hooks/pointerDnd.js.map +1 -1
- package/dist/react/hooks/pragmaticDnd.d.ts +4 -1
- package/dist/react/hooks/pragmaticDnd.d.ts.map +1 -1
- package/dist/react/hooks/pragmaticDnd.js +9 -3
- package/dist/react/hooks/pragmaticDnd.js.map +1 -1
- package/dist/react/hooks/usePublishedBottomChrome.d.ts +12 -0
- package/dist/react/hooks/usePublishedBottomChrome.d.ts.map +1 -0
- package/dist/react/hooks/usePublishedBottomChrome.js +31 -0
- package/dist/react/hooks/usePublishedBottomChrome.js.map +1 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +3 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/lib/selectField.d.ts +72 -0
- package/dist/react/lib/selectField.d.ts.map +1 -0
- package/dist/react/lib/selectField.js +23 -0
- package/dist/react/lib/selectField.js.map +1 -0
- package/dist/react/media/VideoChapterEditor.d.ts.map +1 -1
- package/dist/react/media/VideoChapterEditor.js +2 -1
- package/dist/react/media/VideoChapterEditor.js.map +1 -1
- package/dist/react/media/VideoCutEditor.d.ts.map +1 -1
- package/dist/react/media/VideoCutEditor.js +2 -1
- package/dist/react/media/VideoCutEditor.js.map +1 -1
- package/dist/react/media/denseGallery.css +2 -0
- package/dist/react/media-gallery/MediaGallery.d.ts.map +1 -1
- package/dist/react/media-gallery/MediaGallery.js +6 -7
- package/dist/react/media-gallery/MediaGallery.js.map +1 -1
- package/dist/react/media-gallery/MediaMetaEditor.d.ts.map +1 -1
- package/dist/react/media-gallery/MediaMetaEditor.js +2 -1
- package/dist/react/media-gallery/MediaMetaEditor.js.map +1 -1
- package/dist/react/nav/CommandBar.d.ts.map +1 -1
- package/dist/react/nav/CommandBar.js +2 -1
- package/dist/react/nav/CommandBar.js.map +1 -1
- package/dist/react/palette/CommandPalette.d.ts +15 -1
- package/dist/react/palette/CommandPalette.d.ts.map +1 -1
- package/dist/react/palette/CommandPalette.js +58 -8
- package/dist/react/palette/CommandPalette.js.map +1 -1
- package/dist/react/palette/Kbd.d.ts +15 -0
- package/dist/react/palette/Kbd.d.ts.map +1 -0
- package/dist/react/palette/Kbd.js +17 -0
- package/dist/react/palette/Kbd.js.map +1 -0
- package/dist/react/palette/ShortcutSheet.d.ts +30 -0
- package/dist/react/palette/ShortcutSheet.d.ts.map +1 -0
- package/dist/react/palette/ShortcutSheet.js +15 -0
- package/dist/react/palette/ShortcutSheet.js.map +1 -0
- package/dist/react/palette/commandSources.d.ts +14 -0
- package/dist/react/palette/commandSources.d.ts.map +1 -1
- package/dist/react/palette/commandSources.js +11 -1
- package/dist/react/palette/commandSources.js.map +1 -1
- package/dist/react/palette/index.d.ts +4 -2
- package/dist/react/palette/index.d.ts.map +1 -1
- package/dist/react/palette/index.js +2 -0
- package/dist/react/palette/index.js.map +1 -1
- package/dist/scripts/guardrailsEnforce.d.ts.map +1 -1
- package/dist/styles-areas/canvas-hud.css +2 -2
- package/dist/styles-areas/kanban.css +31 -0
- package/dist/styles-areas/palette.css +3 -3
- package/dist/styles-areas/workbench.css +15 -13
- package/dist/styles-utilities.css +42 -0
- package/package.json +4 -3
- package/scripts/guardrails-baseline.json +2 -1
- package/scripts/guardrailsEnforce.spec.ts +62 -0
- package/scripts/guardrailsEnforce.ts +77 -6
- package/scripts/styleAreas.ts +2 -0
- package/src/react/canvas-hud/HudControls.spec.tsx +11 -0
- package/src/react/canvas-hud/HudControls.tsx +33 -9
- package/src/react/components/AppShell.spec.tsx +40 -0
- package/src/react/components/AppShell.tsx +25 -27
- package/src/react/components/ChipFilterStrip.tsx +2 -38
- package/src/react/components/FileDropZone.spec.tsx +16 -0
- package/src/react/components/FileDropZone.tsx +3 -1
- package/src/react/components/KanbanBoard.spec.tsx +89 -0
- package/src/react/components/KanbanBoard.tsx +270 -0
- package/src/react/components/ResponsiveWorkbench.spec.tsx +57 -0
- package/src/react/components/ResponsiveWorkbench.tsx +69 -4
- package/src/react/components/SelectField.spec.tsx +117 -0
- package/src/react/components/SelectField.tsx +131 -166
- package/src/react/components/SelectFieldImpl.tsx +140 -0
- package/src/react/components/filterChip.tsx +43 -0
- package/src/react/hooks/pointerDnd.spec.ts +89 -3
- package/src/react/hooks/pointerDnd.ts +103 -8
- package/src/react/hooks/pragmaticDnd.ts +13 -2
- package/src/react/hooks/usePublishedBottomChrome.ts +30 -0
- package/src/react/hooks/useScrollMemory.spec.tsx +7 -5
- package/src/react/index.ts +3 -0
- package/src/react/lib/floatingStyles.spec.ts +1 -1
- package/src/react/lib/selectField.ts +96 -0
- package/src/react/media/VideoChapterEditor.tsx +3 -2
- package/src/react/media/VideoCutEditor.tsx +3 -2
- package/src/react/media/denseGallery.css +2 -0
- package/src/react/media-gallery/MediaGallery.spec.tsx +15 -11
- package/src/react/media-gallery/MediaGallery.tsx +16 -18
- package/src/react/media-gallery/MediaMetaEditor.tsx +2 -1
- package/src/react/nav/CommandBar.tsx +2 -3
- package/src/react/palette/CommandPalette.spec.tsx +73 -0
- package/src/react/palette/CommandPalette.tsx +153 -13
- package/src/react/palette/Kbd.tsx +46 -0
- package/src/react/palette/ShortcutSheet.spec.tsx +58 -0
- package/src/react/palette/ShortcutSheet.tsx +110 -0
- package/src/react/palette/commandSources.tsx +30 -1
- package/src/react/palette/index.ts +9 -0
- package/src/styles-areas/canvas-hud.css +2 -2
- package/src/styles-areas/kanban.css +31 -0
- package/src/styles-areas/palette.css +3 -3
- package/src/styles-areas/workbench.css +15 -13
- package/src/styles-utilities.css +42 -0
|
@@ -6,6 +6,8 @@ export * from 'cursedbelt-core/palette';
|
|
|
6
6
|
export { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, } from './Command.js';
|
|
7
7
|
export { CommandSourceProvider, useCommandSource, useCommandSources, } from './commandSources.js';
|
|
8
8
|
export { CommandPalette, } from './CommandPalette.js';
|
|
9
|
+
export { Kbd } from './Kbd.js';
|
|
10
|
+
export { ShortcutSheet, } from './ShortcutSheet.js';
|
|
9
11
|
export { useCommandPalette, } from './useCommandPalette.js';
|
|
10
12
|
export { useHotkeys } from './useHotkeys.js';
|
|
11
13
|
export { useShortcutRegistry } from './useShortcutRegistry.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/react/palette/index.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,yFAAyF;AACzF,6FAA6F;AAC7F,oFAAoF;AAEpF,cAAc,yBAAyB,CAAC;AACxC,OAAO,EACL,OAAO,EACP,YAAY,EACZ,YAAY,EACZ,YAAY,EAEZ,WAAW,EACX,WAAW,EACX,gBAAgB,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/react/palette/index.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,yFAAyF;AACzF,6FAA6F;AAC7F,oFAAoF;AAEpF,cAAc,yBAAyB,CAAC;AACxC,OAAO,EACL,OAAO,EACP,YAAY,EACZ,YAAY,EACZ,YAAY,EAEZ,WAAW,EACX,WAAW,EACX,gBAAgB,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAIL,qBAAqB,EACrB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,cAAc,GAGf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,GAAG,EAAiB,MAAM,UAAU,CAAC;AAC9C,OAAO,EACL,aAAa,GAId,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAGL,iBAAiB,GAClB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAA0B,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guardrailsEnforce.d.ts","sourceRoot":"","sources":["../../scripts/guardrailsEnforce.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"guardrailsEnforce.d.ts","sourceRoot":"","sources":["../../scripts/guardrailsEnforce.ts"],"names":[],"mappings":";AAiQA,UAAU,SAAS;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB;;;sEAGkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,IAAI;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC;CACzD;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CA6B9D;AA2yCD,iGAAiG;AACjG,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAE3D;AAoHD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAIpE;AAs4BD,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,GAAG,IAAI,CAEpF;AAID,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,CAoBhE;AAyKD,kGAAkG;AAClG,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAatE;AAqDD,UAAU,MAAM;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACb;AA0BD,+FAA+F;AAC/F,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAgBpD;AAID,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO5E;AA0FD,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAkCtC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAAiE,CAAC;AAEtH,0EAA0E;AAC1E,eAAO,MAAM,SAAS,EAAE,SAAS,MAAM,EAAwE,CAAC;AAShH,eAAO,MAAM,uBAAuB,aAAkD,CAAC;AAGvF,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACb;AAED;2FAC2F;AAC3F,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CA+C3F;AAED,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAgHhC,wBAAgB,0BAA0B,CAAC,SAAS,GAAE,MAAwB,GAAG,MAAM,EAAE,CAyBxF"}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* after an app's responsive variant (task 2124). An app that imports every area gets exactly
|
|
19
19
|
* what `cursedbelt/styles-utilities.css` emits — proved rule by rule in
|
|
20
20
|
* src/stylesAreas.spec.ts, which is also what fails when this file is stale.
|
|
21
|
-
*
|
|
21
|
+
* 40 candidate(s).
|
|
22
22
|
*
|
|
23
23
|
* Written by scripts/generateAreaStyles.ts (`bun run styles:areas`), which
|
|
24
24
|
* `bun run build` runs. The map of what is in which area, and why the areas are
|
|
@@ -29,4 +29,4 @@
|
|
|
29
29
|
@source inline("[scrollbar-width:none] aria-pressed:bg-muted aria-pressed:text-foreground basis-0 basis-full");
|
|
30
30
|
@source inline("bg-background/40 bg-background/70 bottom-3 bottom-4 focus-visible:border-transparent");
|
|
31
31
|
@source inline("focus-visible:ring-0 left-3 max-w-48 min-w-10 min-w-26 min-w-40 ml-2 opacity-100 order-1 order-2");
|
|
32
|
-
@source inline("order-3 pb-1.5 pl-0 pl-1 place-items-center pr-1 pt-1 px-0.5 right-4 w-40 w-64");
|
|
32
|
+
@source inline("order-3 pb-1.5 pl-0 pl-1 place-items-center pr-1 pt-1 px-0.5 right-3 right-4 w-40 w-64");
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* cursedbelt — the `kanban` area's utility candidates. GENERATED BUILD ARTIFACT. Never hand-edit.
|
|
3
|
+
*
|
|
4
|
+
* the Kanban board — columns of dragged cards; an app with no board renders none of it.
|
|
5
|
+
*
|
|
6
|
+
* Every Tailwind utility the modules in this area's import closure use, MINUS the
|
|
7
|
+
* ones `styles-areas/core.css` already ships. 🔴 Import ALL of these, in
|
|
8
|
+
* this order — this file alone is incomplete:
|
|
9
|
+
*
|
|
10
|
+
* @import "tailwindcss";
|
|
11
|
+
* @import "cursedbelt/styles-static.css";
|
|
12
|
+
* @import "cursedbelt/styles-areas/core.css";
|
|
13
|
+
* @import "cursedbelt/styles-areas/kanban.css";
|
|
14
|
+
* @import "cursedbelt/theme.css";
|
|
15
|
+
*
|
|
16
|
+
* These are CANDIDATES (`@source inline`), not CSS: the app's own Tailwind pass generates
|
|
17
|
+
* them, sorted together with the app's utilities, so an area can never put a base utility
|
|
18
|
+
* after an app's responsive variant (task 2124). An app that imports every area gets exactly
|
|
19
|
+
* what `cursedbelt/styles-utilities.css` emits — proved rule by rule in
|
|
20
|
+
* src/stylesAreas.spec.ts, which is also what fails when this file is stale.
|
|
21
|
+
* 26 candidate(s).
|
|
22
|
+
*
|
|
23
|
+
* Written by scripts/generateAreaStyles.ts (`bun run styles:areas`), which
|
|
24
|
+
* `bun run build` runs. The map of what is in which area, and why the areas are
|
|
25
|
+
* import closures rather than a hand-written list, is scripts/styleAreas.ts.
|
|
26
|
+
*/
|
|
27
|
+
@source inline("[&::-webkit-scrollbar]:hidden [scrollbar-width:none] active:cursor-grabbing bg-accent/15");
|
|
28
|
+
@source inline("border-accent/70 border-dashed cursor-grab focus-visible:border-accent hover:bg-muted/80");
|
|
29
|
+
@source inline("max-h-full max-lg:snap-mandatory max-lg:snap-x max-md:w-full max-w-64 md:hidden min-h min-h-8");
|
|
30
|
+
@source inline("overscroll-x-auto ring-1 ring-accent ring-accent/30 ring-accent/50 ring-inset shadow-2xl");
|
|
31
|
+
@source inline("snap-start w-72");
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* after an app's responsive variant (task 2124). An app that imports every area gets exactly
|
|
19
19
|
* what `cursedbelt/styles-utilities.css` emits — proved rule by rule in
|
|
20
20
|
* src/stylesAreas.spec.ts, which is also what fails when this file is stale.
|
|
21
|
-
*
|
|
21
|
+
* 30 candidate(s).
|
|
22
22
|
*
|
|
23
23
|
* Written by scripts/generateAreaStyles.ts (`bun run styles:areas`), which
|
|
24
24
|
* `bun run build` runs. The map of what is in which area, and why the areas are
|
|
@@ -31,5 +31,5 @@
|
|
|
31
31
|
@source inline("border-dashed data-[disabled=true]:opacity-50 data-[disabled=true]:pointer-events-none");
|
|
32
32
|
@source inline("data-[selected=true]:bg-muted data-[selected=true]:text-foreground h-12 max-h-72");
|
|
33
33
|
@source inline("max-h-[var(--palette-max-h)] max-w-64 mx-1 opacity-100 place-items-center");
|
|
34
|
-
@source inline("placeholder:text-muted-foreground py-8 shadow-popover sm:inline-block
|
|
35
|
-
@source inline("w-72 w-[calc(100%-2rem)]");
|
|
34
|
+
@source inline("placeholder:text-muted-foreground py-8 shadow-popover sm:inline-block sm:inline-flex");
|
|
35
|
+
@source inline("top-[var(--palette-top)] w-72 w-[calc(100%-2rem)]");
|
|
@@ -18,25 +18,27 @@
|
|
|
18
18
|
* after an app's responsive variant (task 2124). An app that imports every area gets exactly
|
|
19
19
|
* what `cursedbelt/styles-utilities.css` emits — proved rule by rule in
|
|
20
20
|
* src/stylesAreas.spec.ts, which is also what fails when this file is stale.
|
|
21
|
-
*
|
|
21
|
+
* 98 candidate(s).
|
|
22
22
|
*
|
|
23
23
|
* Written by scripts/generateAreaStyles.ts (`bun run styles:areas`), which
|
|
24
24
|
* `bun run build` runs. The map of what is in which area, and why the areas are
|
|
25
25
|
* import closures rather than a hand-written list, is scripts/styleAreas.ts.
|
|
26
26
|
*/
|
|
27
27
|
@source inline("-my-0.5 [&::-webkit-scrollbar]:hidden [&_svg]:size-3.5 [scrollbar-width:none] bg-accent/15");
|
|
28
|
-
@source inline("bg-accent/40 bg-accent/8 bg-card-overlay/85 bg-muted/30 bg-muted/60
|
|
29
|
-
@source inline("border-8 border-accent-solid border-accent/70 border-border-strong/60
|
|
30
|
-
@source inline("border-dashed border-solid bottom-6
|
|
31
|
-
@source inline("
|
|
32
|
-
@source inline("
|
|
33
|
-
@source inline("
|
|
34
|
-
@source inline("
|
|
35
|
-
@source inline("
|
|
36
|
-
@source inline("
|
|
37
|
-
@source inline("
|
|
38
|
-
@source inline("
|
|
39
|
-
@source inline("
|
|
28
|
+
@source inline("bg-accent/40 bg-accent/8 bg-background/85 bg-card-overlay/85 bg-muted/30 bg-muted/60");
|
|
29
|
+
@source inline("bg-success/8 border-2 border-8 border-accent-solid border-accent/70 border-border-strong/60");
|
|
30
|
+
@source inline("border-border/50 border-dashed border-solid bottom-6");
|
|
31
|
+
@source inline("bottom-[max(env(safe-area-inset-bottom,0px),0.75rem)] disabled:opacity-0 duration-150 first:pt-1");
|
|
32
|
+
@source inline("flex-row group-hover:disabled:opacity-50 h-105 h-40 hover:bg-card-overlay");
|
|
33
|
+
@source inline("hover:bg-muted-foreground/10 hover:bg-muted/80 hover:border-field-ring hover:shadow-popover");
|
|
34
|
+
@source inline("inset-x-3 italic items-stretch left-2.5 max-h-full min-h min-h-20 min-h-8 min-h-9 min-h-dvh");
|
|
35
|
+
@source inline("min-h-full min-w-20 min-w-40 opacity-100 pb-0.5 pb-[max(env(safe-area-inset-bottom,0px),0.5rem)]");
|
|
36
|
+
@source inline("peer-focus peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-field-ring/40");
|
|
37
|
+
@source inline("peer-focus:rounded-lg pl-1 pl-3 pointer-coarse:disabled:opacity-50 pointer-coarse:h-12");
|
|
38
|
+
@source inline("pointer-coarse:w-12 pr-16 pr-9 pt-1.5 resize-none right-4 ring-1 ring-4 ring-accent/20");
|
|
39
|
+
@source inline("ring-accent/30 ring-accent/40 ring-border ring-inset ring-ring rounded-2xl");
|
|
40
|
+
@source inline("rounded-[var(--radius-device)] scale-[1.01] self-stretch shrink-[2] size-7 sm:flex-1");
|
|
41
|
+
@source inline("sm:flex-nowrap sm:shrink-0 sm:w-56 text-muted-foreground/60 text-tone-amber-ink");
|
|
40
42
|
@source inline("text-tone-cyan-ink text-tone-emerald-ink text-tone-orange-ink text-tone-purple-ink");
|
|
41
43
|
@source inline("text-tone-rose-ink text-tone-sky-ink text-tone-slate-ink text-xl transition-[opacity,transform]");
|
|
42
44
|
@source inline("translate-y-0 translate-y-2 w-0 w-88");
|
|
@@ -297,6 +297,9 @@
|
|
|
297
297
|
.bottom-6 {
|
|
298
298
|
bottom: calc(var(--spacing) * 6);
|
|
299
299
|
}
|
|
300
|
+
.bottom-\[max\(env\(safe-area-inset-bottom\,0px\)\,0\.75rem\)\] {
|
|
301
|
+
bottom: max(env(safe-area-inset-bottom,0px), 0.75rem);
|
|
302
|
+
}
|
|
300
303
|
.-left-1\.5 {
|
|
301
304
|
left: calc(var(--spacing) * -1.5);
|
|
302
305
|
}
|
|
@@ -1323,6 +1326,9 @@
|
|
|
1323
1326
|
.resize-y {
|
|
1324
1327
|
resize: vertical;
|
|
1325
1328
|
}
|
|
1329
|
+
.snap-start {
|
|
1330
|
+
scroll-snap-align: start;
|
|
1331
|
+
}
|
|
1326
1332
|
.\[scrollbar-width\:none\] {
|
|
1327
1333
|
scrollbar-width: none;
|
|
1328
1334
|
}
|
|
@@ -1576,6 +1582,9 @@
|
|
|
1576
1582
|
.overscroll-contain {
|
|
1577
1583
|
overscroll-behavior: contain;
|
|
1578
1584
|
}
|
|
1585
|
+
.overscroll-x-auto {
|
|
1586
|
+
overscroll-behavior-x: auto;
|
|
1587
|
+
}
|
|
1579
1588
|
.rounded {
|
|
1580
1589
|
border-radius: 0.25rem;
|
|
1581
1590
|
}
|
|
@@ -2648,6 +2657,9 @@
|
|
|
2648
2657
|
.pt-1 {
|
|
2649
2658
|
padding-top: var(--spacing);
|
|
2650
2659
|
}
|
|
2660
|
+
.pt-1\.5 {
|
|
2661
|
+
padding-top: calc(var(--spacing) * 1.5);
|
|
2662
|
+
}
|
|
2651
2663
|
.pt-2 {
|
|
2652
2664
|
padding-top: calc(var(--spacing) * 2);
|
|
2653
2665
|
}
|
|
@@ -3201,6 +3213,10 @@
|
|
|
3201
3213
|
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
|
3202
3214
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
3203
3215
|
}
|
|
3216
|
+
.shadow-2xl {
|
|
3217
|
+
--tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25));
|
|
3218
|
+
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
3219
|
+
}
|
|
3204
3220
|
.shadow-\[0_0_0_1px_rgba\(0\,0\,0\,0\.55\)\] {
|
|
3205
3221
|
--tw-shadow: 0 0 0 1px var(--tw-shadow-color, rgba(0,0,0,0.55));
|
|
3206
3222
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
@@ -3295,6 +3311,12 @@
|
|
|
3295
3311
|
--tw-ring-color: color-mix(in oklab, var(--accent) 40%, transparent);
|
|
3296
3312
|
}
|
|
3297
3313
|
}
|
|
3314
|
+
.ring-accent\/50 {
|
|
3315
|
+
--tw-ring-color: var(--accent);
|
|
3316
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
3317
|
+
--tw-ring-color: color-mix(in oklab, var(--accent) 50%, transparent);
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3298
3320
|
.ring-background {
|
|
3299
3321
|
--tw-ring-color: var(--background);
|
|
3300
3322
|
}
|
|
@@ -3979,6 +4001,9 @@
|
|
|
3979
4001
|
--tw-outline-style: none;
|
|
3980
4002
|
outline-style: none;
|
|
3981
4003
|
}
|
|
4004
|
+
.focus-visible\:border-accent:focus-visible {
|
|
4005
|
+
border-color: var(--accent);
|
|
4006
|
+
}
|
|
3982
4007
|
.focus-visible\:border-border-strong:focus-visible {
|
|
3983
4008
|
border-color: var(--border-strong);
|
|
3984
4009
|
}
|
|
@@ -4482,6 +4507,14 @@
|
|
|
4482
4507
|
scale: var(--tw-scale-x) var(--tw-scale-y);
|
|
4483
4508
|
}
|
|
4484
4509
|
}
|
|
4510
|
+
@media (width < 64rem) {
|
|
4511
|
+
.max-lg\:snap-x {
|
|
4512
|
+
scroll-snap-type: x var(--tw-scroll-snap-strictness);
|
|
4513
|
+
}
|
|
4514
|
+
.max-lg\:snap-mandatory {
|
|
4515
|
+
--tw-scroll-snap-strictness: mandatory;
|
|
4516
|
+
}
|
|
4517
|
+
}
|
|
4485
4518
|
@media (width < 48rem) {
|
|
4486
4519
|
.max-md\:absolute {
|
|
4487
4520
|
position: absolute;
|
|
@@ -4495,6 +4528,9 @@
|
|
|
4495
4528
|
.max-md\:hidden {
|
|
4496
4529
|
display: none;
|
|
4497
4530
|
}
|
|
4531
|
+
.max-md\:w-full {
|
|
4532
|
+
width: 100%;
|
|
4533
|
+
}
|
|
4498
4534
|
}
|
|
4499
4535
|
@media (width < 40rem) {
|
|
4500
4536
|
.max-sm\:inset-x-0 {
|
|
@@ -5785,6 +5821,11 @@
|
|
|
5785
5821
|
inherits: false;
|
|
5786
5822
|
initial-value: 1;
|
|
5787
5823
|
}
|
|
5824
|
+
@property --tw-scroll-snap-strictness {
|
|
5825
|
+
syntax: "*";
|
|
5826
|
+
inherits: false;
|
|
5827
|
+
initial-value: proximity;
|
|
5828
|
+
}
|
|
5788
5829
|
@property --tw-content {
|
|
5789
5830
|
syntax: "*";
|
|
5790
5831
|
initial-value: "";
|
|
@@ -5915,6 +5956,7 @@
|
|
|
5915
5956
|
--tw-scale-x: 1;
|
|
5916
5957
|
--tw-scale-y: 1;
|
|
5917
5958
|
--tw-scale-z: 1;
|
|
5959
|
+
--tw-scroll-snap-strictness: proximity;
|
|
5918
5960
|
--tw-content: "";
|
|
5919
5961
|
}
|
|
5920
5962
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursedbelt",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.79.1",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "The React design system of the cursedbelt split — components, styles, theme. cursedbelt-core below it; server tier in cursedbelt-server.",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"demo:check": "tsc --noEmit --project demo/tsconfig.json",
|
|
76
76
|
"demo:paths": "bun run demo/generatePaths.ts --write",
|
|
77
77
|
"demo:coverage": "bun run scripts/demoCoverage.ts",
|
|
78
|
-
"guardrails:report": "bun run scripts/guardrailsEnforce.ts no-raw-action-button,raw-button-resets-face,no-raw-overflow,no-raw-inputs,no-hardcoded-colors,no-raw-modal-footer,no-unguarded-editing-modal,control-height-tokens,no-raw-zindex,no-raw-data-table,no-hand-rolled-property-list,no-tailwind-palette,no-raw-title-attr,micro-typography-floor,choice-needs-empty-state,no-select-as-action-menu,no-native-select,no-native-dialog,no-fast-tooltip,no-raw-password,no-raw-lazy,no-raw-popover,no-hand-rolled-scroll-memory,no-hand-rolled-surface,no-inline-style,no-arbitrary-length,opt-out-count,opt-out-needs-cause --scope cursedbelt src/react src/styles.css",
|
|
78
|
+
"guardrails:report": "bun run scripts/guardrailsEnforce.ts no-raw-action-button,raw-button-resets-face,no-raw-overflow,no-raw-inputs,no-hardcoded-colors,no-raw-modal-footer,no-unguarded-editing-modal,control-height-tokens,no-raw-zindex,no-raw-data-table,no-hand-rolled-property-list,no-raw-kbd,no-tailwind-palette,no-raw-title-attr,micro-typography-floor,choice-needs-empty-state,no-select-as-action-menu,no-native-select,no-native-dialog,no-fast-tooltip,no-raw-password,no-raw-lazy,no-raw-popover,no-hand-rolled-scroll-memory,no-hand-rolled-surface,no-inline-style,no-arbitrary-length,opt-out-count,opt-out-needs-cause --scope cursedbelt src/react src/styles.css",
|
|
79
79
|
"lint": "biome lint --error-on-warnings src e2e demo scripts",
|
|
80
80
|
"styles": "bun run styles:static && bun run styles:utilities && bun run styles:areas",
|
|
81
81
|
"styles:static": "bun run scripts/generateStaticStyles.ts",
|
|
@@ -657,6 +657,7 @@
|
|
|
657
657
|
"./react/file-tree/geometry.json": "./src/react/file-tree/fileTreeGeometry.fixture.json",
|
|
658
658
|
"./styles-areas/filter-rail.css": "./src/styles-areas/filter-rail.css",
|
|
659
659
|
"./styles-areas/folder-tree.css": "./src/styles-areas/folder-tree.css",
|
|
660
|
+
"./styles-areas/kanban.css": "./src/styles-areas/kanban.css",
|
|
660
661
|
"./styles-areas/keep-awake.css": "./src/styles-areas/keep-awake.css",
|
|
661
662
|
"./styles-areas/layout-engine.css": "./src/styles-areas/layout-engine.css",
|
|
662
663
|
"./styles-areas/markdown.css": "./src/styles-areas/markdown.css",
|
|
@@ -972,7 +973,7 @@
|
|
|
972
973
|
"class-variance-authority": "^0.7.1",
|
|
973
974
|
"clsx": "^2.1.1",
|
|
974
975
|
"cmdk": "^1.1.1",
|
|
975
|
-
"cursedbelt-core": "^2.
|
|
976
|
+
"cursedbelt-core": "^2.13.1",
|
|
976
977
|
"cwip": "^4.12.0",
|
|
977
978
|
"lucide-react": "^1.21.0",
|
|
978
979
|
"react-hook-form": "^7.80.0",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"no-unguarded-editing-modal": 0,
|
|
12
12
|
"no-raw-data-table": 0,
|
|
13
13
|
"no-hand-rolled-property-list": 0,
|
|
14
|
+
"no-raw-kbd": 0,
|
|
14
15
|
"no-tailwind-palette": 0,
|
|
15
16
|
"no-raw-title-attr": 0,
|
|
16
17
|
"no-local-reimplementation": 0,
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
"no-hand-rolled-surface": 0,
|
|
23
24
|
"no-inline-style": 73,
|
|
24
25
|
"no-arbitrary-length": 0,
|
|
25
|
-
"opt-out-count":
|
|
26
|
+
"opt-out-count": 273,
|
|
26
27
|
"opt-out-needs-cause": 255
|
|
27
28
|
},
|
|
28
29
|
"cursedshell": {
|
|
@@ -1054,6 +1054,15 @@ describe('no-native-select — the OS draws its list (owner, 2026-10-10)', () =>
|
|
|
1054
1054
|
expect(check('no-native-select', source)).toHaveLength(2);
|
|
1055
1055
|
});
|
|
1056
1056
|
|
|
1057
|
+
test('a raw <select> is the same OS list, and counts too (forge task 3097)', () => {
|
|
1058
|
+
const source = `<select className="cs-select" value={rule.field} onChange={(e) => change(e.target.value)}>
|
|
1059
|
+
{FIELDS.map((f) => <option key={f}>{f}</option>)}
|
|
1060
|
+
</select>`;
|
|
1061
|
+
expect(check('no-native-select', source)).toHaveLength(1);
|
|
1062
|
+
expect(check('no-native-select', `{/* a \`<select>\` here would be an OS list */}\n<SelectField options={o} />`)).toHaveLength(0);
|
|
1063
|
+
expect(check('no-native-select', `<selection-bar />`)).toHaveLength(0);
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1057
1066
|
test('the styled controls, and a comment that names it, are not one', () => {
|
|
1058
1067
|
expect(check('no-native-select', `<SelectField value={sort} options={SORTS} />`)).toHaveLength(0);
|
|
1059
1068
|
expect(check('no-native-select', `// was a <NativeSelect> until 2026-10-10\n<PropertyPicker name="Type" />`)).toHaveLength(0);
|
|
@@ -1442,6 +1451,36 @@ describe('--baseline (a consuming repo\'s ratchet)', () => {
|
|
|
1442
1451
|
);
|
|
1443
1452
|
});
|
|
1444
1453
|
|
|
1454
|
+
test('🔴 a row for a rule this belt does not have yet only warns — the app is behind a bump, not drifting (task 3098)', async () => {
|
|
1455
|
+
// 2026-10-13: a new rule's row went into every app's baseline with the belt still
|
|
1456
|
+
// unpublished, and the six apps on the LOCKED belt went red at once.
|
|
1457
|
+
await withFixture(
|
|
1458
|
+
{ ...FIXTURE, 'scripts/baseline.json': JSON.stringify({ 'no-hardcoded-colors': 2, 'no-rule-from-the-future': 0 }) },
|
|
1459
|
+
async (dir) => {
|
|
1460
|
+
const { code, report } = await runCli(dir, ['no-hardcoded-colors', '--baseline', 'scripts/baseline.json', 'src']);
|
|
1461
|
+
expect(report).toContain('this cursedbelt does not have yet: no-rule-from-the-future');
|
|
1462
|
+
expect(report).not.toContain('nobody scans');
|
|
1463
|
+
expect(report).toContain('✓ no-hardcoded-colors: 2 violation(s)');
|
|
1464
|
+
expect(code).toBe(0);
|
|
1465
|
+
},
|
|
1466
|
+
);
|
|
1467
|
+
});
|
|
1468
|
+
|
|
1469
|
+
test('a row ahead of the belt does not excuse a row the belt knows and does not scan', async () => {
|
|
1470
|
+
await withFixture(
|
|
1471
|
+
{
|
|
1472
|
+
...FIXTURE,
|
|
1473
|
+
'scripts/baseline.json': JSON.stringify({ 'no-hardcoded-colors': 2, 'no-raw-zindex': 0, 'no-rule-from-the-future': 0 }),
|
|
1474
|
+
},
|
|
1475
|
+
async (dir) => {
|
|
1476
|
+
const { code, report } = await runCli(dir, ['no-hardcoded-colors', '--baseline', 'scripts/baseline.json', 'src']);
|
|
1477
|
+
expect(code).toBe(2);
|
|
1478
|
+
expect(report).toContain('row(s) for rules nobody scans: no-raw-zindex');
|
|
1479
|
+
expect(report).not.toContain('nobody scans: no-raw-zindex, no-rule-from-the-future');
|
|
1480
|
+
},
|
|
1481
|
+
);
|
|
1482
|
+
});
|
|
1483
|
+
|
|
1445
1484
|
test('a scanned rule with no row is refused — a silent 0 demands the whole pile at once', async () => {
|
|
1446
1485
|
await withFixture({ ...FIXTURE, 'scripts/baseline.json': JSON.stringify({ 'no-hardcoded-colors': 2 }) }, async (dir) => {
|
|
1447
1486
|
const { code, report } = await runCli(dir, [
|
|
@@ -1651,6 +1690,29 @@ describe('no-hand-rolled-property-list', () => {
|
|
|
1651
1690
|
});
|
|
1652
1691
|
});
|
|
1653
1692
|
|
|
1693
|
+
describe('no-raw-kbd', () => {
|
|
1694
|
+
const rule = RULES['no-raw-kbd'];
|
|
1695
|
+
test('a raw <kbd> in app source is a violation — desk\'s switcher foot drew its own', () => {
|
|
1696
|
+
const found = rule.check('src/ui/QuickSwitcher.tsx', `<footer>\n <kbd>↑</kbd> move\n</footer>`);
|
|
1697
|
+
expect(found).toHaveLength(1);
|
|
1698
|
+
expect(found[0]?.line).toBe(2);
|
|
1699
|
+
});
|
|
1700
|
+
test('the belt\'s Kbd is the fix, and is not flagged', () => {
|
|
1701
|
+
expect(rule.check('src/ui/QuickSwitcher.tsx', `<Kbd keys="$mod+k" /> <Kbd>↑</Kbd>`)).toHaveLength(0);
|
|
1702
|
+
});
|
|
1703
|
+
test('an opt-out with a reason on the block above is honoured; a comment mentioning <kbd> is not a violation', () => {
|
|
1704
|
+
expect(
|
|
1705
|
+
rule.check('src/ui/Menu.tsx', `// guardrails-ignore no-raw-kbd: a menu accelerator is bare text,\n// not a cap\n<kbd>{shortcut}</kbd>`),
|
|
1706
|
+
).toHaveLength(0);
|
|
1707
|
+
expect(rule.check('src/ui/Menu.tsx', `// the old <kbd> here was hand-drawn`)).toHaveLength(0);
|
|
1708
|
+
});
|
|
1709
|
+
test('exempt by construction: Kbd itself, fixtures and stylesheets', () => {
|
|
1710
|
+
for (const file of ['src/react/palette/Kbd.tsx', 'src/ui/Foot.spec.tsx', 'src/ui/foot.css']) {
|
|
1711
|
+
expect(rule.check(file, `<kbd>K</kbd>`)).toHaveLength(0);
|
|
1712
|
+
}
|
|
1713
|
+
});
|
|
1714
|
+
});
|
|
1715
|
+
|
|
1654
1716
|
describe('no-raw-nav', () => {
|
|
1655
1717
|
test('🔴 flags a hand-rolled <nav> — the header music shipped on 2026-09-25', () => {
|
|
1656
1718
|
expect(
|
|
@@ -76,6 +76,10 @@
|
|
|
76
76
|
// `Property`. On the ELEMENT, like `no-raw-data-table`. The belt's own PropertyList, the
|
|
77
77
|
// markdown/rich-text renderers and `*.spec|test.*` fixtures are exempt by construction.
|
|
78
78
|
//
|
|
79
|
+
// no-raw-kbd — forge task 3087 (2026-10-13): no hand-drawn `<kbd>` — a key is cursedbelt's
|
|
80
|
+
// `Kbd` (`<Kbd keys="$mod+k" />` → ⌘K / Ctrl K), and an app's `?` list is `ShortcutSheet`.
|
|
81
|
+
// On the ELEMENT. The belt's own `Kbd`, stylesheets and `*.spec|test.*` fixtures are exempt.
|
|
82
|
+
//
|
|
79
83
|
// no-raw-data-table — DESIGN.md §3 "Visual Formatting & Data Presentation" (owner, 2026-08-14): no hand-rolled `<table>` —
|
|
80
84
|
// use `cursedbelt/react/data-table`'s `DataTable`, which carries sorting, a global
|
|
81
85
|
// filter, column visibility, pagination and the phone card tier. The rule is on the
|
|
@@ -113,7 +117,7 @@
|
|
|
113
117
|
// Semantic classes are never matched — `text-xs` IS 12px. A non-text glyph takes
|
|
114
118
|
// the per-line opt-out.
|
|
115
119
|
//
|
|
116
|
-
// no-native-select — owner, 2026-10-10: a `NativeSelect`'s list is drawn by the OS, where no
|
|
120
|
+
// no-native-select — owner, 2026-10-10: a `NativeSelect`'s (or raw `<select>`'s) list is drawn by the OS, where no
|
|
117
121
|
// stylesheet reaches it, so it breaks the app's cohesion. `SelectField` / `ComboboxField` /
|
|
118
122
|
// `PropertyPicker` / `ChoiceMenu` instead; a use where the OS picker is genuinely better says
|
|
119
123
|
// why with `// guardrails-ignore no-native-select: <why>` on the line above. Ratcheted per app.
|
|
@@ -1057,6 +1061,7 @@ const CONTROL_HEIGHT_FILES = new Set([
|
|
|
1057
1061
|
"IconButton.tsx",
|
|
1058
1062
|
"Input.tsx",
|
|
1059
1063
|
"SelectField.tsx",
|
|
1064
|
+
"SelectFieldImpl.tsx",
|
|
1060
1065
|
"ComboboxField.tsx",
|
|
1061
1066
|
"Autocomplete.tsx",
|
|
1062
1067
|
"TagsInput.tsx",
|
|
@@ -1296,6 +1301,48 @@ const noHandRolledPropertyList: Rule = {
|
|
|
1296
1301
|
},
|
|
1297
1302
|
};
|
|
1298
1303
|
|
|
1304
|
+
// ── no-raw-kbd (forge task 3087, 2026-10-13) ───────────────────────────────────
|
|
1305
|
+
// By 2026-10-13 four apps drew the same key four ways: vault's cap was mono with a 2px bottom
|
|
1306
|
+
// border, desk's a bare browser `<kbd>`, family's its own grey box, music's another — and ⌘ showed
|
|
1307
|
+
// to a Windows reader wherever a glyph had been typed by hand. cursedbelt 5.79.0's `Kbd` is the
|
|
1308
|
+
// one cap, formatted per platform from a binding (`<Kbd keys="$mod+k" />`), or drawn as written
|
|
1309
|
+
// from children (`<Kbd>↑</Kbd>`); `ShortcutSheet` is the `?` list built from it.
|
|
1310
|
+
//
|
|
1311
|
+
// So the rule is on the ELEMENT, like `no-hand-rolled-property-list`: a `<kbd` in app source is
|
|
1312
|
+
// a violation. Exempt by construction: `Kbd` itself, `DropdownMenu`'s accelerator column (bare
|
|
1313
|
+
// text in the platform's notation, as macOS's and Linear's menus draw it — not a cap), and
|
|
1314
|
+
// `*.spec|test.*` fixtures. A key named in
|
|
1315
|
+
// prose is still a key — it is `<Kbd>`; a BUTTON named in prose is `<strong>`, never a cap.
|
|
1316
|
+
const RAW_KBD_RE = /<kbd\b/g;
|
|
1317
|
+
const RAW_KBD_IGNORE_RE = /guardrails-ignore\s+no-raw-kbd\s*:/;
|
|
1318
|
+
const RAW_KBD_EXEMPT_FRAGMENTS = ["react/palette/Kbd.tsx", "react/components/DropdownMenu.tsx"];
|
|
1319
|
+
|
|
1320
|
+
const noRawKbd: Rule = {
|
|
1321
|
+
name: "no-raw-kbd",
|
|
1322
|
+
description:
|
|
1323
|
+
"forge task 3087: no hand-drawn <kbd> — a key cap is cursedbelt's Kbd (cursedbelt/react: <Kbd keys=\"$mod+k\" /> draws ⌘K on a Mac and Ctrl K elsewhere; <Kbd>↑</Kbd> draws a literal), and an app's ? list is ShortcutSheet. A raw <kbd> is how every app picks its own cap, and how ⌘ reaches a Windows reader.",
|
|
1324
|
+
check(filePath, source) {
|
|
1325
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
1326
|
+
if (extname(normalized) === ".css" || isTestSource(normalized)) return [];
|
|
1327
|
+
if (RAW_KBD_EXEMPT_FRAGMENTS.some((fragment) => normalized.includes(fragment))) return [];
|
|
1328
|
+
const violations: Violation[] = [];
|
|
1329
|
+
const scannable = blankComments(source);
|
|
1330
|
+
const lines = source.split("\n");
|
|
1331
|
+
for (const match of scannable.matchAll(RAW_KBD_RE)) {
|
|
1332
|
+
const line = lineOf(source, match.index ?? 0);
|
|
1333
|
+
const block: string[] = [];
|
|
1334
|
+
for (let i = line - 2; i >= 0 && block.length < 12; i--) {
|
|
1335
|
+
const raw = lines[i] ?? "";
|
|
1336
|
+
if (raw.trim() === "") break;
|
|
1337
|
+
block.push(raw);
|
|
1338
|
+
}
|
|
1339
|
+
if (RAW_KBD_IGNORE_RE.test(block.join("\n"))) continue;
|
|
1340
|
+
violations.push({ file: filePath, line, snippet: lines[line - 1]?.trim() ?? "" });
|
|
1341
|
+
}
|
|
1342
|
+
return violations;
|
|
1343
|
+
},
|
|
1344
|
+
};
|
|
1345
|
+
|
|
1299
1346
|
// ── no-unguarded-editing-modal ──────────────────────────────────────────────
|
|
1300
1347
|
// The owner lost a half-typed password by clicking outside an editing modal,
|
|
1301
1348
|
// which dismissed it and threw the form away with no confirmation. `Modal`/
|
|
@@ -2041,7 +2088,9 @@ const noSelectAsActionMenu: Rule = {
|
|
|
2041
2088
|
// macOS Chrome a grey system menu no stylesheet reaches — so every app that used one had a control
|
|
2042
2089
|
// that was not the kit's. `no-select-as-action-menu` caught only the action-menu shape of it.
|
|
2043
2090
|
//
|
|
2044
|
-
// Every app now counts its `<NativeSelect` uses
|
|
2091
|
+
// Every app now counts its `<NativeSelect` uses — and its raw `<select>`s, the same OS list without
|
|
2092
|
+
// even the kit's frame (forge task 3097: desk had eight under `no-raw-inputs`, which no count of
|
|
2093
|
+
// NativeSelect could see) — in its two-sided ratchet, so the number only falls:
|
|
2045
2094
|
// `SelectField` / `ComboboxField` for a field, `PropertyPicker` for a record's property pill,
|
|
2046
2095
|
// `ChoiceMenu` to choose-and-act. A use that is genuinely right — a form on a phone where the OS
|
|
2047
2096
|
// wheel IS the better control — says so on the line above:
|
|
@@ -2051,18 +2100,19 @@ const NATIVE_SELECT_IGNORE_RE = /guardrails-ignore\s+no-native-select\s*:/;
|
|
|
2051
2100
|
const noNativeSelect: Rule = {
|
|
2052
2101
|
name: "no-native-select",
|
|
2053
2102
|
description:
|
|
2054
|
-
"Owner, 2026-10-10: unstyled dropdowns break the app's cohesiveness. A `NativeSelect`'s list is drawn by the OS, outside the page, where no stylesheet reaches it. Use `SelectField` / `ComboboxField` for a form field, `PropertyPicker` for a record's property (Type, Folder, Tags), or `ChoiceMenu` to choose-and-act. Where the OS picker is the better control, say why on the line above: `// guardrails-ignore no-native-select: <why>`.",
|
|
2103
|
+
"Owner, 2026-10-10: unstyled dropdowns break the app's cohesiveness. A `NativeSelect`'s (or a raw `<select>`'s) list is drawn by the OS, outside the page, where no stylesheet reaches it. Use `SelectField` / `ComboboxField` for a form field, `PropertyPicker` for a record's property (Type, Folder, Tags), or `ChoiceMenu` to choose-and-act. Where the OS picker is the better control, say why on the line above: `// guardrails-ignore no-native-select: <why>`.",
|
|
2055
2104
|
check(filePath, source) {
|
|
2056
2105
|
const unix = filePath.replace(/\\/g, "/");
|
|
2057
2106
|
// The kit is held to its own law (forge task 3063: its DataTable filter builder and
|
|
2058
2107
|
// FocusViewer drew OS menus into every app whose count read 0). Only the control itself
|
|
2059
2108
|
// may render one.
|
|
2060
2109
|
if (/(^|\/)(?:packages|libs)\/cursedbelt\/src\/react\/components\/NativeSelect\.tsx$/.test(unix)) return [];
|
|
2110
|
+
if (isBeltOwnSource(filePath) && /(^|\/)src\/react\/components\/NativeSelect\.tsx$/.test(unix)) return [];
|
|
2061
2111
|
if (/\.(?:spec|test)\.[tj]sx?$/.test(unix)) return [];
|
|
2062
2112
|
const scannable = blankComments(source);
|
|
2063
2113
|
const lines = source.split("\n");
|
|
2064
2114
|
const out: Violation[] = [];
|
|
2065
|
-
for (const match of scannable.matchAll(/<NativeSelect\b/g)) {
|
|
2115
|
+
for (const match of scannable.matchAll(/<(?:NativeSelect|select)\b/g)) {
|
|
2066
2116
|
const line = lineOf(source, match.index ?? 0);
|
|
2067
2117
|
if (NATIVE_SELECT_IGNORE_RE.test(lines[line - 1] ?? "") || NATIVE_SELECT_IGNORE_RE.test(blockAbove(lines, line))) continue;
|
|
2068
2118
|
out.push({ file: filePath, line, snippet: lines[line - 1]?.trim() ?? "" });
|
|
@@ -3022,6 +3072,7 @@ export const RULES: Record<string, Rule> = {
|
|
|
3022
3072
|
[noRawZindex.name]: noRawZindex,
|
|
3023
3073
|
[noRawDataTable.name]: noRawDataTable,
|
|
3024
3074
|
[noHandRolledPropertyList.name]: noHandRolledPropertyList,
|
|
3075
|
+
[noRawKbd.name]: noRawKbd,
|
|
3025
3076
|
[noTailwindPalette.name]: noTailwindPalette,
|
|
3026
3077
|
[themeColoursResolve.name]: themeColoursResolve,
|
|
3027
3078
|
[noRawTitleAttr.name]: noRawTitleAttr,
|
|
@@ -3206,12 +3257,25 @@ function parseFlatBaseline(json: string, label: string): FlatBaseline {
|
|
|
3206
3257
|
function baselineDisagreements(rows: FlatBaseline, ruleNames: readonly string[]): string[] {
|
|
3207
3258
|
const complaints: string[] = [];
|
|
3208
3259
|
const missing = ruleNames.filter((name) => !(name in rows));
|
|
3209
|
-
const orphans = Object.keys(rows).filter((name) => !ruleNames.includes(name));
|
|
3260
|
+
const orphans = Object.keys(rows).filter((name) => !ruleNames.includes(name) && name in RULES);
|
|
3210
3261
|
if (missing.length > 0) complaints.push(`no row for rule(s) being scanned: ${missing.join(", ")}`);
|
|
3211
3262
|
if (orphans.length > 0) complaints.push(`row(s) for rules nobody scans: ${orphans.join(", ")}`);
|
|
3212
3263
|
return complaints;
|
|
3213
3264
|
}
|
|
3214
3265
|
|
|
3266
|
+
/**
|
|
3267
|
+
* Rows naming a rule this belt has never heard of — forge task 3098. A new rule lands
|
|
3268
|
+
* in the belt's tree and every consumer's baseline gets its row in the same pass, but a
|
|
3269
|
+
* consumer still on the LOCKED belt then carries a row ahead of its library: not drift,
|
|
3270
|
+
* a bump not made yet. Refusing it red every app off the tree at once (2026-10-13, six
|
|
3271
|
+
* fast-lane deploys). It is a warning; a row this belt KNOWS and does not scan is still
|
|
3272
|
+
* refused above, and a mistyped row cannot hide anything — the real rule's row is then
|
|
3273
|
+
* missing, which is red.
|
|
3274
|
+
*/
|
|
3275
|
+
function rowsAheadOfThisBelt(rows: FlatBaseline, ruleNames: readonly string[]): string[] {
|
|
3276
|
+
return Object.keys(rows).filter((name) => !ruleNames.includes(name) && !(name in RULES));
|
|
3277
|
+
}
|
|
3278
|
+
|
|
3215
3279
|
// ── `--reimplements-from-belt`: the name list, off this package's own exports ───
|
|
3216
3280
|
// `no-local-reimplementation` matches app-declared names against the ones cursedbelt
|
|
3217
3281
|
// exports (384 of them as this lands). A hand-kept copy of a dependency's export list
|
|
@@ -3322,6 +3386,13 @@ function main() {
|
|
|
3322
3386
|
console.error((error as Error).message);
|
|
3323
3387
|
process.exit(2);
|
|
3324
3388
|
}
|
|
3389
|
+
const ahead = rowsAheadOfThisBelt(flatBaseline, ruleNames);
|
|
3390
|
+
if (ahead.length > 0) {
|
|
3391
|
+
console.error(
|
|
3392
|
+
`⚠ ${baselinePath} has row(s) for rule(s) this cursedbelt does not have yet: ${ahead.join(", ")} — ` +
|
|
3393
|
+
"not scanned; they apply once the app is on a cursedbelt that ships them.",
|
|
3394
|
+
);
|
|
3395
|
+
}
|
|
3325
3396
|
const complaints = baselineDisagreements(flatBaseline, ruleNames);
|
|
3326
3397
|
if (complaints.length > 0) {
|
|
3327
3398
|
console.error(`${baselinePath} and the rules being scanned disagree:`);
|
|
@@ -3329,7 +3400,7 @@ function main() {
|
|
|
3329
3400
|
// A row with no rule stops here. A rule with no row — what a belt bump that adds a
|
|
3330
3401
|
// rule produces — is still scanned, so the run prints the exact row to record
|
|
3331
3402
|
// instead of making the bumping agent find the count by hand; it exits 2 at the end.
|
|
3332
|
-
if (Object.keys(flatBaseline).some((name) => !ruleNames.includes(name))) process.exit(2);
|
|
3403
|
+
if (Object.keys(flatBaseline).some((name) => !ruleNames.includes(name) && name in RULES)) process.exit(2);
|
|
3333
3404
|
for (const name of ruleNames) if (!(name in flatBaseline)) missingRows.add(name);
|
|
3334
3405
|
}
|
|
3335
3406
|
}
|
package/scripts/styleAreas.ts
CHANGED
|
@@ -195,6 +195,7 @@ export const AREAS: readonly Area[] = [
|
|
|
195
195
|
'components/Section.tsx',
|
|
196
196
|
'components/SegmentedControl.tsx',
|
|
197
197
|
'components/SelectField.tsx',
|
|
198
|
+
'components/SelectFieldImpl.tsx',
|
|
198
199
|
'components/Skeleton.tsx',
|
|
199
200
|
'components/Slider.tsx',
|
|
200
201
|
'components/Spinner.tsx',
|
|
@@ -291,6 +292,7 @@ export const AREAS: readonly Area[] = [
|
|
|
291
292
|
// The breadcrumb's menus and pivot are `import()`ed by `nav/BreadcrumbTrail.tsx` (lazyImportFault).
|
|
292
293
|
roots: ['folder-tree', 'nav/BreadcrumbMenus.tsx', 'nav/BreadcrumbPivot.tsx'],
|
|
293
294
|
},
|
|
295
|
+
{ name: 'kanban', why: 'the Kanban board — columns of dragged cards; an app with no board renders none of it', roots: ['components/KanbanBoard.tsx'] },
|
|
294
296
|
{ name: 'keep-awake', why: 'the keep-awake toggle and its badge', roots: ['keep-awake'] },
|
|
295
297
|
{ name: 'layout-engine', why: 'the persisted pane/grid layout engine — apps with a fixed layout render none of it', roots: ['layoutEngine'] },
|
|
296
298
|
{ name: 'markdown', why: 'the rendered-markdown document', roots: ['markdown', 'markdown/MarkdownDoc.tsx'] },
|
|
@@ -26,6 +26,17 @@ describe('HudZoomControls (CNV-3)', () => {
|
|
|
26
26
|
expect(calls).toEqual(['out', 'in', 'fit', 'center']);
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
+
test('vertical: a column with + on top and − under the percentage, announced as one', () => {
|
|
30
|
+
const { getByRole } = render(
|
|
31
|
+
<HudZoomControls orientation='vertical' scale={0.5} onZoomIn={() => {}} onZoomOut={() => {}} onFit={() => {}} />,
|
|
32
|
+
);
|
|
33
|
+
const group = getByRole('group', { name: 'View' });
|
|
34
|
+
expect(group.getAttribute('aria-orientation')).toBe('vertical');
|
|
35
|
+
expect(group.className).toContain('flex-col');
|
|
36
|
+
const names = Array.from(group.querySelectorAll('button')).map((b) => b.getAttribute('aria-label'));
|
|
37
|
+
expect(names).toEqual(['Zoom in', 'Zoom out', 'Fit to screen']);
|
|
38
|
+
});
|
|
39
|
+
|
|
29
40
|
test('no Center key when the canvas has nothing to centre on', () => {
|
|
30
41
|
const { queryByRole } = render(
|
|
31
42
|
<HudZoomControls scale={1} onZoomIn={() => {}} onZoomOut={() => {}} onFit={() => {}} />,
|