cursedbelt 5.55.2 → 5.58.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.
- package/dist/react/charts/tokens.js +4 -4
- package/dist/react/code-editor/CodeEditor.d.ts.map +1 -1
- package/dist/react/code-editor/CodeEditor.js +2 -1
- package/dist/react/code-editor/CodeEditor.js.map +1 -1
- package/dist/react/code-editor/lineAbove.d.ts +17 -0
- package/dist/react/code-editor/lineAbove.d.ts.map +1 -0
- package/dist/react/code-editor/lineAbove.js +34 -0
- package/dist/react/code-editor/lineAbove.js.map +1 -0
- package/dist/react/companion-link/CompanionLinkStatus.d.ts.map +1 -1
- package/dist/react/companion-link/CompanionLinkStatus.js +2 -1
- package/dist/react/companion-link/CompanionLinkStatus.js.map +1 -1
- package/dist/react/components/Accordion.d.ts.map +1 -1
- package/dist/react/components/Accordion.js +2 -2
- package/dist/react/components/Accordion.js.map +1 -1
- package/dist/react/components/Badge.d.ts +20 -1
- package/dist/react/components/Badge.d.ts.map +1 -1
- package/dist/react/components/Badge.js +30 -3
- package/dist/react/components/Badge.js.map +1 -1
- package/dist/react/components/Button.d.ts.map +1 -1
- package/dist/react/components/Button.js +8 -2
- package/dist/react/components/Button.js.map +1 -1
- package/dist/react/components/Collapsible.d.ts.map +1 -1
- package/dist/react/components/Collapsible.js +2 -2
- package/dist/react/components/Collapsible.js.map +1 -1
- package/dist/react/components/DisclosureButton.d.ts +2 -2
- package/dist/react/components/DisclosureButton.d.ts.map +1 -1
- package/dist/react/components/DisclosureButton.js +9 -6
- package/dist/react/components/DisclosureButton.js.map +1 -1
- package/dist/react/components/EditorPage.d.ts +78 -0
- package/dist/react/components/EditorPage.d.ts.map +1 -0
- package/dist/react/components/EditorPage.js +133 -0
- package/dist/react/components/EditorPage.js.map +1 -0
- package/dist/react/components/IconPicker.d.ts +70 -0
- package/dist/react/components/IconPicker.d.ts.map +1 -0
- package/dist/react/components/IconPicker.js +175 -0
- package/dist/react/components/IconPicker.js.map +1 -0
- package/dist/react/components/PropertyList.d.ts +45 -0
- package/dist/react/components/PropertyList.d.ts.map +1 -0
- package/dist/react/components/PropertyList.js +12 -0
- package/dist/react/components/PropertyList.js.map +1 -0
- package/dist/react/components/Wizard/ReviewStep.d.ts +1 -1
- package/dist/react/components/Wizard/ReviewStep.d.ts.map +1 -1
- package/dist/react/components/Wizard/ReviewStep.js +3 -3
- package/dist/react/components/Wizard/ReviewStep.js.map +1 -1
- package/dist/react/components/editorPage.css +421 -0
- package/dist/react/components/formKit.d.ts +3 -1
- package/dist/react/components/formKit.d.ts.map +1 -1
- package/dist/react/components/formKit.js +3 -1
- package/dist/react/components/formKit.js.map +1 -1
- package/dist/react/file-tree/FileTree.d.ts.map +1 -1
- package/dist/react/file-tree/FileTree.js +7 -0
- package/dist/react/file-tree/FileTree.js.map +1 -1
- package/dist/react/file-tree/fileTree.css +14 -0
- package/dist/react/filter-rail/FilterRail.js +1 -1
- package/dist/react/filter-rail/FilterRail.js.map +1 -1
- package/dist/react/filter-rail/RailSection.js +2 -2
- package/dist/react/filter-rail/RailSection.js.map +1 -1
- package/dist/react/filter-rail/headerContextMenu.d.ts.map +1 -1
- package/dist/react/filter-rail/headerContextMenu.js +20 -1
- package/dist/react/filter-rail/headerContextMenu.js.map +1 -1
- package/dist/react/hooks/longPressContextMenu.d.ts +35 -0
- package/dist/react/hooks/longPressContextMenu.d.ts.map +1 -0
- package/dist/react/hooks/longPressContextMenu.js +136 -0
- package/dist/react/hooks/longPressContextMenu.js.map +1 -0
- package/dist/react/hooks/useLongPressContextMenu.d.ts +4 -0
- package/dist/react/hooks/useLongPressContextMenu.d.ts.map +1 -0
- package/dist/react/hooks/useLongPressContextMenu.js +12 -0
- package/dist/react/hooks/useLongPressContextMenu.js.map +1 -0
- package/dist/react/index.d.ts +4 -1
- 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/line-keys/index.d.ts +2 -0
- package/dist/react/line-keys/index.d.ts.map +1 -0
- package/dist/react/line-keys/index.js +2 -0
- package/dist/react/line-keys/index.js.map +1 -0
- package/dist/react/line-keys/lineKeys.d.ts +62 -0
- package/dist/react/line-keys/lineKeys.d.ts.map +1 -0
- package/dist/react/line-keys/lineKeys.js +159 -0
- package/dist/react/line-keys/lineKeys.js.map +1 -0
- package/dist/react/master-detail/DetailPane.d.ts +3 -0
- package/dist/react/master-detail/DetailPane.d.ts.map +1 -1
- package/dist/react/master-detail/DetailPane.js +5 -2
- package/dist/react/master-detail/DetailPane.js.map +1 -1
- package/dist/react/nav/SideNav.js +1 -1
- package/dist/react/nav/SideNav.js.map +1 -1
- package/dist/scripts/guardrailsEnforce.d.ts.map +1 -1
- package/dist/styles-areas/analytics.css +14 -14
- package/dist/styles-areas/charts.css +3 -3
- package/dist/styles-areas/companion-link.css +2 -2
- package/dist/styles-areas/core.css +50 -45
- package/dist/styles-areas/data-table.css +15 -16
- package/dist/styles-areas/disk-usage.css +4 -4
- package/dist/styles-areas/fields.css +11 -11
- package/dist/styles-areas/folder-tree.css +3 -3
- package/dist/styles-areas/layout-engine.css +2 -2
- package/dist/styles-areas/master-detail.css +4 -5
- package/dist/styles-areas/media-gallery.css +9 -9
- package/dist/styles-areas/media.css +3 -3
- package/dist/styles-areas/nav.css +5 -5
- package/dist/styles-areas/notifications.css +3 -3
- package/dist/styles-areas/problem-detail.css +3 -3
- package/dist/styles-areas/sharing.css +2 -3
- package/dist/styles-areas/stats.css +3 -3
- package/dist/styles-areas/wizard.css +7 -8
- package/dist/styles-areas/workbench.css +4 -4
- package/dist/styles-utilities.css +49 -10
- package/dist/theme.css +38 -22
- package/package.json +14 -2
- package/scripts/guardrails-baseline.json +2 -1
- package/scripts/guardrailsEnforce.spec.ts +32 -0
- package/scripts/guardrailsEnforce.ts +67 -1
- package/scripts/styleAreas.ts +3 -0
- package/src/react/charts/tokens.ts +4 -4
- package/src/react/code-editor/CodeEditor.tsx +2 -1
- package/src/react/code-editor/lineAbove.spec.ts +36 -0
- package/src/react/code-editor/lineAbove.ts +35 -0
- package/src/react/companion-link/CompanionLinkStatus.tsx +10 -12
- package/src/react/components/Accordion.tsx +5 -4
- package/src/react/components/Badge.capsule.spec.ts +1 -1
- package/src/react/components/Badge.spec.tsx +22 -1
- package/src/react/components/Badge.tsx +66 -11
- package/src/react/components/Button.spec.tsx +5 -4
- package/src/react/components/Button.tsx +8 -2
- package/src/react/components/Collapsible.tsx +5 -4
- package/src/react/components/DisclosureButton.spec.tsx +47 -0
- package/src/react/components/DisclosureButton.tsx +11 -7
- package/src/react/components/EditorPage.spec.tsx +106 -0
- package/src/react/components/EditorPage.tsx +281 -0
- package/src/react/components/IconPicker.spec.tsx +84 -0
- package/src/react/components/IconPicker.tsx +289 -0
- package/src/react/components/PropertyList.spec.tsx +110 -0
- package/src/react/components/PropertyList.tsx +92 -0
- package/src/react/components/Wizard/ReviewStep.tsx +7 -7
- package/src/react/components/editorPage.css +421 -0
- package/src/react/components/formKit.ts +10 -1
- package/src/react/components/formKitStylesheets.spec.ts +47 -0
- package/src/react/file-tree/FileTree.spec.tsx +19 -0
- package/src/react/file-tree/FileTree.tsx +8 -0
- package/src/react/file-tree/fileTree.css +14 -0
- package/src/react/filter-rail/FilterRail.tsx +1 -1
- package/src/react/filter-rail/RailSection.tsx +2 -2
- package/src/react/filter-rail/headerContextMenu.spec.tsx +52 -0
- package/src/react/filter-rail/headerContextMenu.ts +19 -1
- package/src/react/hooks/longPressContextMenu.spec.ts +102 -0
- package/src/react/hooks/longPressContextMenu.ts +135 -0
- package/src/react/hooks/useLongPressContextMenu.ts +15 -0
- package/src/react/index.ts +11 -1
- package/src/react/line-keys/index.ts +1 -0
- package/src/react/line-keys/lineKeys.spec.tsx +176 -0
- package/src/react/line-keys/lineKeys.ts +176 -0
- package/src/react/master-detail/DetailPane.tsx +14 -18
- package/src/react/nav/SideNav.tsx +1 -1
- package/src/styles-areas/analytics.css +14 -14
- package/src/styles-areas/charts.css +3 -3
- package/src/styles-areas/companion-link.css +2 -2
- package/src/styles-areas/core.css +50 -45
- package/src/styles-areas/data-table.css +15 -16
- package/src/styles-areas/disk-usage.css +4 -4
- package/src/styles-areas/fields.css +11 -11
- package/src/styles-areas/folder-tree.css +3 -3
- package/src/styles-areas/layout-engine.css +2 -2
- package/src/styles-areas/master-detail.css +4 -5
- package/src/styles-areas/media-gallery.css +9 -9
- package/src/styles-areas/media.css +3 -3
- package/src/styles-areas/nav.css +5 -5
- package/src/styles-areas/notifications.css +3 -3
- package/src/styles-areas/problem-detail.css +3 -3
- package/src/styles-areas/sharing.css +2 -3
- package/src/styles-areas/stats.css +3 -3
- package/src/styles-areas/wizard.css +7 -8
- package/src/styles-areas/workbench.css +4 -4
- package/src/styles-utilities.css +49 -10
- package/src/theme.css +38 -22
|
@@ -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
|
|
@@ -29,5 +29,4 @@
|
|
|
29
29
|
@source inline("data-[state=open]:slide-in-from-bottom data-[state=open]:slide-in-from-left");
|
|
30
30
|
@source inline("data-[state=open]:slide-in-from-right disabled:opacity-100 hover:opacity-90 left-0 left-3");
|
|
31
31
|
@source inline("max-h-56 max-h-[var(--sheet-h-lg)] max-h-[var(--sheet-h-md)] max-h-[var(--sheet-h-sm)] max-w-sm");
|
|
32
|
-
@source inline("min-w-48 ml-2 mt-4 pl-9 right-3 rounded-l-xl rounded-r-xl rounded-t-xl size-9 space-y-4");
|
|
33
|
-
@source inline("text-[var(--muted-foreground)] w-32");
|
|
32
|
+
@source inline("min-w-48 ml-2 mt-4 pl-9 right-3 rounded-l-xl rounded-r-xl rounded-t-xl size-9 space-y-4 w-32");
|
|
@@ -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
|
+
* 59 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
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
*/
|
|
27
27
|
@source inline("[&>div]:sm:[grid-template-columns:…] [&_svg]:size-3.5 basis-40 bg-accent/15 bg-accent/40");
|
|
28
28
|
@source inline("bg-background/70 border-l-2 border-l-border-strong border-l-destructive border-l-warning");
|
|
29
|
-
@source inline("bottom-3 col-span-2 col-start-2 collapse cursor-help divide-x first:pl-1 gap-x-2 gap-
|
|
30
|
-
@source inline("
|
|
29
|
+
@source inline("bottom-3 col-span-2 col-start-2 collapse cursor-help divide-x first:pl-1 gap-x-2 gap-y-0.5");
|
|
30
|
+
@source inline("grid-cols-[minmax(0,1fr)_auto] group-focus-visible/meter:opacity-100");
|
|
31
31
|
@source inline("group-focus-visible/stat:opacity-100 group-focus-visible/tile:opacity-100");
|
|
32
32
|
@source inline("group-hover/meter:opacity-100 group-hover/stat:opacity-100 group-hover/tile:opacity-100");
|
|
33
33
|
@source inline("group/meter group/stat group/tile hover:bg-muted/50 last:pr-1 left-3");
|
|
@@ -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
|
+
* 58 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
|
|
@@ -32,10 +32,9 @@
|
|
|
32
32
|
@source inline("data-[state=open]:slide-in-from-bottom data-[state=open]:slide-in-from-left");
|
|
33
33
|
@source inline("data-[state=open]:slide-in-from-right disabled:bg-transparent disabled:opacity-100 duration-100");
|
|
34
34
|
@source inline("focus-visible:ring-field-ring/40 focus-within:border-field-ring focus-within:ring-2");
|
|
35
|
-
@source inline("focus-within:ring-field-ring/30
|
|
36
|
-
@source inline("
|
|
37
|
-
@source inline("
|
|
38
|
-
@source inline("
|
|
39
|
-
@source inline("
|
|
40
|
-
@source inline("
|
|
41
|
-
@source inline("transition-[transform,opacity] w-36 w-88");
|
|
35
|
+
@source inline("focus-within:ring-field-ring/30 h-105 has-[[aria-invalid=true]]:border-field-error");
|
|
36
|
+
@source inline("hover:border-field-ring hover:text-warning left-0 max-h-64 max-h-[var(--sheet-h-lg)]");
|
|
37
|
+
@source inline("max-h-[var(--sheet-h-md)] max-h-[var(--sheet-h-sm)] max-w-sm min-h-20 min-w-24");
|
|
38
|
+
@source inline("min-w-[var(--anchor-width)] mr-1.5 mr-2 pl-1.5 pl-2 pr-1 pr-16 pr-3 resize-none resize-y");
|
|
39
|
+
@source inline("ring-field-error/30 rounded-l-xl rounded-r-xl rounded-t-xl self-stretch size-7 sm:inline");
|
|
40
|
+
@source inline("text-field-placeholder text-xl transition-[transform,opacity] w-36 w-88");
|
|
@@ -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
|
+
* 108 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
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
@source inline("peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-field-ring/40 peer-focus:rounded-lg");
|
|
40
40
|
@source inline("pl-1 pl-3 pointer-coarse:disabled:opacity-50 pointer-coarse:h-12 pointer-coarse:w-12 pr-16 pr-9");
|
|
41
41
|
@source inline("resize-none right-4 ring-1 ring-4 ring-accent/20 ring-accent/30 ring-accent/40 ring-border");
|
|
42
|
-
@source inline("ring-inset ring-ring
|
|
43
|
-
@source inline("
|
|
44
|
-
@source inline("
|
|
42
|
+
@source inline("ring-inset ring-ring rounded-[var(--radius-device)] rounded-l-xl rounded-r-xl rounded-t-xl");
|
|
43
|
+
@source inline("scale-[1.01] self-stretch shrink-[2] size-7 sm:flex-1 sm:flex-nowrap sm:shrink-0 sm:w-56");
|
|
44
|
+
@source inline("text-muted-foreground/60 text-tone-amber-ink text-tone-cyan-ink text-tone-emerald-ink");
|
|
45
45
|
@source inline("text-tone-orange-ink text-tone-purple-ink text-tone-rose-ink text-tone-sky-ink");
|
|
46
46
|
@source inline("text-tone-slate-ink text-xl transition-[opacity,transform] translate-y-0 translate-y-2 w-0 w-88");
|
|
@@ -1325,14 +1325,11 @@
|
|
|
1325
1325
|
.grid-cols-12 {
|
|
1326
1326
|
grid-template-columns: repeat(12, minmax(0, 1fr));
|
|
1327
1327
|
}
|
|
1328
|
-
.grid-cols-\[auto_1fr\] {
|
|
1329
|
-
grid-template-columns: auto 1fr;
|
|
1330
|
-
}
|
|
1331
1328
|
.grid-cols-\[minmax\(0\,1fr\)_auto\] {
|
|
1332
1329
|
grid-template-columns: minmax(0,1fr) auto;
|
|
1333
1330
|
}
|
|
1334
|
-
.grid-cols-\[minmax\(
|
|
1335
|
-
grid-template-columns: minmax(
|
|
1331
|
+
.grid-cols-\[minmax\(0\,var\(--property-label-w\)\)_minmax\(0\,1fr\)\] {
|
|
1332
|
+
grid-template-columns: minmax(0,var(--property-label-w)) minmax(0,1fr);
|
|
1336
1333
|
}
|
|
1337
1334
|
.grid-cols-\[repeat\(7\,minmax\(0\,1fr\)\)\] {
|
|
1338
1335
|
grid-template-columns: repeat(7,minmax(0,1fr));
|
|
@@ -1469,15 +1466,15 @@
|
|
|
1469
1466
|
margin-inline-start: calc(calc(var(--spacing) * -2) * var(--tw-space-x-reverse));
|
|
1470
1467
|
margin-inline-end: calc(calc(var(--spacing) * -2) * calc(1 - var(--tw-space-x-reverse)));
|
|
1471
1468
|
}
|
|
1469
|
+
.gap-y-0 {
|
|
1470
|
+
row-gap: 0px;
|
|
1471
|
+
}
|
|
1472
1472
|
.gap-y-0\.5 {
|
|
1473
1473
|
row-gap: calc(var(--spacing) * 0.5);
|
|
1474
1474
|
}
|
|
1475
1475
|
.gap-y-1 {
|
|
1476
1476
|
row-gap: var(--spacing);
|
|
1477
1477
|
}
|
|
1478
|
-
.gap-y-1\.5 {
|
|
1479
|
-
row-gap: calc(var(--spacing) * 1.5);
|
|
1480
|
-
}
|
|
1481
1478
|
.gap-y-2 {
|
|
1482
1479
|
row-gap: calc(var(--spacing) * 2);
|
|
1483
1480
|
}
|
|
@@ -2269,6 +2266,9 @@
|
|
|
2269
2266
|
background-color: color-mix(in oklab, var(--tone-amber-bg) 50%, transparent);
|
|
2270
2267
|
}
|
|
2271
2268
|
}
|
|
2269
|
+
.bg-tone-amber-fg {
|
|
2270
|
+
background-color: var(--tone-amber-fg);
|
|
2271
|
+
}
|
|
2272
2272
|
.bg-tone-cyan-bg {
|
|
2273
2273
|
background-color: var(--tone-cyan-bg);
|
|
2274
2274
|
}
|
|
@@ -2278,6 +2278,9 @@
|
|
|
2278
2278
|
background-color: color-mix(in oklab, var(--tone-cyan-bg) 50%, transparent);
|
|
2279
2279
|
}
|
|
2280
2280
|
}
|
|
2281
|
+
.bg-tone-cyan-fg {
|
|
2282
|
+
background-color: var(--tone-cyan-fg);
|
|
2283
|
+
}
|
|
2281
2284
|
.bg-tone-emerald-bg {
|
|
2282
2285
|
background-color: var(--tone-emerald-bg);
|
|
2283
2286
|
}
|
|
@@ -2287,15 +2290,27 @@
|
|
|
2287
2290
|
background-color: color-mix(in oklab, var(--tone-emerald-bg) 50%, transparent);
|
|
2288
2291
|
}
|
|
2289
2292
|
}
|
|
2293
|
+
.bg-tone-emerald-fg {
|
|
2294
|
+
background-color: var(--tone-emerald-fg);
|
|
2295
|
+
}
|
|
2290
2296
|
.bg-tone-fuchsia-bg {
|
|
2291
2297
|
background-color: var(--tone-fuchsia-bg);
|
|
2292
2298
|
}
|
|
2299
|
+
.bg-tone-fuchsia-fg {
|
|
2300
|
+
background-color: var(--tone-fuchsia-fg);
|
|
2301
|
+
}
|
|
2293
2302
|
.bg-tone-lime-bg {
|
|
2294
2303
|
background-color: var(--tone-lime-bg);
|
|
2295
2304
|
}
|
|
2305
|
+
.bg-tone-lime-fg {
|
|
2306
|
+
background-color: var(--tone-lime-fg);
|
|
2307
|
+
}
|
|
2296
2308
|
.bg-tone-orange-bg {
|
|
2297
2309
|
background-color: var(--tone-orange-bg);
|
|
2298
2310
|
}
|
|
2311
|
+
.bg-tone-orange-fg {
|
|
2312
|
+
background-color: var(--tone-orange-fg);
|
|
2313
|
+
}
|
|
2299
2314
|
.bg-tone-purple-bg {
|
|
2300
2315
|
background-color: var(--tone-purple-bg);
|
|
2301
2316
|
}
|
|
@@ -2305,6 +2320,9 @@
|
|
|
2305
2320
|
background-color: color-mix(in oklab, var(--tone-purple-bg) 50%, transparent);
|
|
2306
2321
|
}
|
|
2307
2322
|
}
|
|
2323
|
+
.bg-tone-purple-fg {
|
|
2324
|
+
background-color: var(--tone-purple-fg);
|
|
2325
|
+
}
|
|
2308
2326
|
.bg-tone-rose-bg {
|
|
2309
2327
|
background-color: var(--tone-rose-bg);
|
|
2310
2328
|
}
|
|
@@ -2314,6 +2332,9 @@
|
|
|
2314
2332
|
background-color: color-mix(in oklab, var(--tone-rose-bg) 50%, transparent);
|
|
2315
2333
|
}
|
|
2316
2334
|
}
|
|
2335
|
+
.bg-tone-rose-fg {
|
|
2336
|
+
background-color: var(--tone-rose-fg);
|
|
2337
|
+
}
|
|
2317
2338
|
.bg-tone-sky-bg {
|
|
2318
2339
|
background-color: var(--tone-sky-bg);
|
|
2319
2340
|
}
|
|
@@ -2323,6 +2344,9 @@
|
|
|
2323
2344
|
background-color: color-mix(in oklab, var(--tone-sky-bg) 50%, transparent);
|
|
2324
2345
|
}
|
|
2325
2346
|
}
|
|
2347
|
+
.bg-tone-sky-fg {
|
|
2348
|
+
background-color: var(--tone-sky-fg);
|
|
2349
|
+
}
|
|
2326
2350
|
.bg-tone-slate-bg {
|
|
2327
2351
|
background-color: var(--tone-slate-bg);
|
|
2328
2352
|
}
|
|
@@ -2332,12 +2356,21 @@
|
|
|
2332
2356
|
background-color: color-mix(in oklab, var(--tone-slate-bg) 50%, transparent);
|
|
2333
2357
|
}
|
|
2334
2358
|
}
|
|
2359
|
+
.bg-tone-slate-fg {
|
|
2360
|
+
background-color: var(--tone-slate-fg);
|
|
2361
|
+
}
|
|
2335
2362
|
.bg-tone-teal-bg {
|
|
2336
2363
|
background-color: var(--tone-teal-bg);
|
|
2337
2364
|
}
|
|
2365
|
+
.bg-tone-teal-fg {
|
|
2366
|
+
background-color: var(--tone-teal-fg);
|
|
2367
|
+
}
|
|
2338
2368
|
.bg-tone-violet-bg {
|
|
2339
2369
|
background-color: var(--tone-violet-bg);
|
|
2340
2370
|
}
|
|
2371
|
+
.bg-tone-violet-fg {
|
|
2372
|
+
background-color: var(--tone-violet-fg);
|
|
2373
|
+
}
|
|
2341
2374
|
.bg-transparent {
|
|
2342
2375
|
background-color: transparent;
|
|
2343
2376
|
}
|
|
@@ -3488,8 +3521,14 @@
|
|
|
3488
3521
|
.group-active\/sep\:opacity-100:is(:where(.group\/sep):active *) {
|
|
3489
3522
|
opacity: 100%;
|
|
3490
3523
|
}
|
|
3491
|
-
.group-data-\[
|
|
3492
|
-
|
|
3524
|
+
.group-data-\[density\=compact\]\/properties\:min-h-6:is(:where(.group\/properties)[data-density="compact"] *) {
|
|
3525
|
+
min-height: calc(var(--spacing) * 6);
|
|
3526
|
+
}
|
|
3527
|
+
.group-data-\[density\=compact\]\/properties\:items-start:is(:where(.group\/properties)[data-density="compact"] *) {
|
|
3528
|
+
align-items: flex-start;
|
|
3529
|
+
}
|
|
3530
|
+
.group-data-\[state\=open\]\:rotate-90:is(:where(.group)[data-state="open"] *) {
|
|
3531
|
+
rotate: 90deg;
|
|
3493
3532
|
}
|
|
3494
3533
|
.group-data-\[state\=open\]\/rail-section\:rotate-90:is(:where(.group\/rail-section)[data-state="open"] *) {
|
|
3495
3534
|
rotate: 90deg;
|
package/dist/theme.css
CHANGED
|
@@ -261,6 +261,13 @@
|
|
|
261
261
|
--control-h-md: 36px;
|
|
262
262
|
--control-h-lg: 40px;
|
|
263
263
|
|
|
264
|
+
/* ---- Property list label column ----------------------------------------- */
|
|
265
|
+
/* The ONE label column a `PropertyList` shares (FRM-1, FRM-2): wide enough for
|
|
266
|
+
"Target date" and "The done box" at 14px, narrow enough that the value column
|
|
267
|
+
starts where Linear's does. An app with longer labels answers this token on
|
|
268
|
+
its own scope; it never sizes a row's label to its own text. */
|
|
269
|
+
--property-label-w: 8rem;
|
|
270
|
+
|
|
264
271
|
/* ---- Panel/card body default padding (task cbux-41) --------------------- */
|
|
265
272
|
/* The default inner padding for a card/panel BODY (Surface's `md` pad, a
|
|
266
273
|
Modal's content area, a Field group). Not theme-dependent. */
|
|
@@ -509,34 +516,43 @@
|
|
|
509
516
|
clears 7:1+ on its own tints in dark, so re-bind to that instead. */
|
|
510
517
|
--accent-strong: var(--accent);
|
|
511
518
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
519
|
+
/* 🔴 NEUTRAL since 5.58.0 — Linear's ramp, not a navy one. The owner put desk under Linear and
|
|
520
|
+
asked a judge which had the better UI (2026-10-05); the verdict praised Linear's "very
|
|
521
|
+
disciplined dark palette with minimal, purpose-driven pops of color". The grounds here were
|
|
522
|
+
slate-blue (#030712 / #0b1220 / #1f2937, hue 220 at 30–50% saturation), so every surface was
|
|
523
|
+
already a colour before a single status hue was drawn on it, and an accent or a warning had
|
|
524
|
+
to shout to be seen. These are the same lightness steps at under 8% saturation: the chrome
|
|
525
|
+
recedes and the hues that mean something are the only colour on the page (FND-10). Every
|
|
526
|
+
ratio the old values were tuned to is re-measured beside its token below. */
|
|
527
|
+
--background: #08090a; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
528
|
+
--foreground: #f7f8f8; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
529
|
+
--surface: #101113; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
530
|
+
--card: #101113; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
531
|
+
--card-foreground: #f7f8f8; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
532
|
+
/* Elevation reads through the bg here: flat (--surface #101113) < raised
|
|
518
533
|
(--card-raised) < overlay (--card-overlay), each a step lighter than the last
|
|
519
|
-
above the near-black --background (#
|
|
534
|
+
above the near-black --background (#08090a). Shadows are near-invisible on
|
|
520
535
|
black, so the lighter bg + stronger border carry the depth. */
|
|
521
|
-
--card-raised: #
|
|
522
|
-
--card-overlay: #
|
|
523
|
-
--popover: #
|
|
524
|
-
--popover-foreground: #
|
|
525
|
-
--muted: #
|
|
526
|
-
/* 7.
|
|
536
|
+
--card-raised: #16171a; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
537
|
+
--card-overlay: #1d1e22; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
538
|
+
--popover: #17181b; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
539
|
+
--popover-foreground: #f7f8f8; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
540
|
+
--muted: #202126; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
541
|
+
/* 7.06:1 on --card (#101113), 6.0:1 on --muted — the audited dark secondary-text token; the field
|
|
527
542
|
placeholder re-binds to this (no lighter dark step exists worth the risk). */
|
|
528
|
-
--muted-foreground: #
|
|
543
|
+
--muted-foreground: #9b9ea7; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
529
544
|
--field-placeholder: var(--muted-foreground);
|
|
530
545
|
/* Hairlines: the old #1f2937 vanished against --card (1.28:1) — dark-mode rows
|
|
531
|
-
read as one unbroken wall. Lifted a step; still quiet.
|
|
532
|
-
|
|
533
|
-
/*
|
|
534
|
-
|
|
546
|
+
read as one unbroken wall. Lifted a step; still quiet.
|
|
547
|
+
1.5:1 on --card since the neutral ramp (5.58.0). */
|
|
548
|
+
--border: #31333a; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
549
|
+
/* Interactive edges: 3.28:1 vs --card — clears the WCAG 3:1 non-text floor. */
|
|
550
|
+
--border-strong: #62666f; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
535
551
|
/* Re-derived against THIS scope's --border — see the note at :root. */
|
|
536
552
|
--rule: color-mix(in srgb, var(--border) 55%, transparent);
|
|
537
|
-
/* dark: sits between --background (#
|
|
553
|
+
/* dark: sits between --background (#08090a) and --card (#101113) — recessed
|
|
538
554
|
relative to the card it's usually nested inside. */
|
|
539
|
-
--surface-sunken: #
|
|
555
|
+
--surface-sunken: #0b0c0d; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
540
556
|
|
|
541
557
|
--destructive: #ef4444; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
542
558
|
--destructive-foreground: #ffffff; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
@@ -555,9 +571,9 @@
|
|
|
555
571
|
/* The neon the star always had — correct against a near-black canvas at 10.76:1,
|
|
556
572
|
and the half of the pair that never needed changing. See the note at :root. */
|
|
557
573
|
--star: #f5c518; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
558
|
-
/* Same rule in dark — see the note at :root. Measured 3.49:1 on --card (#
|
|
574
|
+
/* Same rule in dark — see the note at :root. Measured 3.49:1 on --card (#101113)
|
|
559
575
|
and 3.66:1 on the dark aurora canvas. */
|
|
560
|
-
--star-empty: #
|
|
576
|
+
--star-empty: #666a74; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
561
577
|
/* Dark badge text: the base green/amber already clear 6:1 on the dark tints;
|
|
562
578
|
red needs the lighter step (#ef4444 was 3.9:1 on its tint). */
|
|
563
579
|
--success-strong: #22c55e; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursedbelt",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.58.0",
|
|
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.",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"demo:check": "tsc --noEmit --project demo/tsconfig.json",
|
|
73
73
|
"demo:paths": "bun run demo/generatePaths.ts --write",
|
|
74
74
|
"demo:coverage": "bun run scripts/demoCoverage.ts",
|
|
75
|
-
"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-tailwind-palette,no-raw-title-attr,micro-typography-floor,choice-needs-empty-state,no-select-as-action-menu,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",
|
|
75
|
+
"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-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",
|
|
76
76
|
"lint": "biome lint --error-on-warnings src e2e demo scripts",
|
|
77
77
|
"styles": "bun run styles:static && bun run styles:utilities && bun run styles:areas",
|
|
78
78
|
"styles:static": "bun run scripts/generateStaticStyles.ts",
|
|
@@ -312,6 +312,12 @@
|
|
|
312
312
|
"source": "./src/react/keep-awake/index.ts",
|
|
313
313
|
"import": "./dist/react/keep-awake/index.js"
|
|
314
314
|
},
|
|
315
|
+
"./react/line-keys": {
|
|
316
|
+
"types": "./dist/react/line-keys/index.d.ts",
|
|
317
|
+
"bun": "./src/react/line-keys/index.ts",
|
|
318
|
+
"source": "./src/react/line-keys/index.ts",
|
|
319
|
+
"import": "./dist/react/line-keys/index.js"
|
|
320
|
+
},
|
|
315
321
|
"./react/markdown-doc": {
|
|
316
322
|
"types": "./dist/react/markdown/MarkdownDoc.d.ts",
|
|
317
323
|
"bun": "./src/react/markdown/MarkdownDoc.tsx",
|
|
@@ -396,6 +402,12 @@
|
|
|
396
402
|
"source": "./src/react/components/Popover.tsx",
|
|
397
403
|
"import": "./dist/react/components/Popover.js"
|
|
398
404
|
},
|
|
405
|
+
"./react/property-list": {
|
|
406
|
+
"types": "./dist/react/components/PropertyList.d.ts",
|
|
407
|
+
"bun": "./src/react/components/PropertyList.tsx",
|
|
408
|
+
"source": "./src/react/components/PropertyList.tsx",
|
|
409
|
+
"import": "./dist/react/components/PropertyList.js"
|
|
410
|
+
},
|
|
399
411
|
"./react/prompt": {
|
|
400
412
|
"types": "./dist/react/prompt.d.ts",
|
|
401
413
|
"bun": "./src/react/prompt.tsx",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"no-raw-zindex": 0,
|
|
11
11
|
"no-unguarded-editing-modal": 0,
|
|
12
12
|
"no-raw-data-table": 0,
|
|
13
|
+
"no-hand-rolled-property-list": 0,
|
|
13
14
|
"no-tailwind-palette": 0,
|
|
14
15
|
"no-raw-title-attr": 0,
|
|
15
16
|
"no-local-reimplementation": 0,
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
"no-hand-rolled-surface": 0,
|
|
22
23
|
"no-inline-style": 73,
|
|
23
24
|
"no-arbitrary-length": 0,
|
|
24
|
-
"opt-out-count":
|
|
25
|
+
"opt-out-count": 273,
|
|
25
26
|
"opt-out-needs-cause": 256
|
|
26
27
|
},
|
|
27
28
|
"cursedshell": {
|
|
@@ -552,6 +552,15 @@ describe('no-unguarded-editing-modal', () => {
|
|
|
552
552
|
expect(appFile('<Drawer open onClose={close}><input value={v} /></Drawer>')).toHaveLength(1);
|
|
553
553
|
});
|
|
554
554
|
|
|
555
|
+
test('flags an EditorPage the same way — Back and Escape leave it as a dismiss leaves a Modal', () => {
|
|
556
|
+
expect(
|
|
557
|
+
appFile('<EditorPage title="New" onBack={close} preview={card}><Input value={v} /></EditorPage>'),
|
|
558
|
+
).toHaveLength(1);
|
|
559
|
+
expect(
|
|
560
|
+
appFile('<EditorPage title="New" onBack={close} isDirty={dirty} preview={card}><Input value={v} /></EditorPage>'),
|
|
561
|
+
).toHaveLength(0);
|
|
562
|
+
});
|
|
563
|
+
|
|
555
564
|
test('accepts the same Modal once it passes isDirty', () => {
|
|
556
565
|
expect(
|
|
557
566
|
appFile('<Modal open onClose={close} isDirty={isDirty}><Input value={v} /></Modal>'),
|
|
@@ -1508,6 +1517,29 @@ const html = \`<input type="password">\`;`,
|
|
|
1508
1517
|
});
|
|
1509
1518
|
});
|
|
1510
1519
|
|
|
1520
|
+
describe('no-hand-rolled-property-list', () => {
|
|
1521
|
+
const rule = RULES['no-hand-rolled-property-list'];
|
|
1522
|
+
test('a raw <dl> in app source is a violation — the pane the owner lost to Linear on', () => {
|
|
1523
|
+
const found = rule.check('src/ui/Panel.tsx', `<dl className="cs-cmppanel__facts">\n <div><dt>Opens as</dt><dd>Checklist</dd></div>\n</dl>`);
|
|
1524
|
+
expect(found).toHaveLength(1);
|
|
1525
|
+
expect(found[0]?.line).toBe(1);
|
|
1526
|
+
});
|
|
1527
|
+
test('the belt\'s PropertyList is the fix, and is not flagged', () => {
|
|
1528
|
+
expect(rule.check('src/ui/Panel.tsx', `<PropertyList><Property label="Opens as">Checklist</Property></PropertyList>`)).toHaveLength(0);
|
|
1529
|
+
});
|
|
1530
|
+
test('an opt-out with a reason on the block above is honoured; a comment mentioning <dl> is not a violation', () => {
|
|
1531
|
+
expect(
|
|
1532
|
+
rule.check('src/ui/Doc.tsx', `{/* guardrails-ignore no-hand-rolled-property-list: this renders the\n document's own definition list, which is content, not chrome */}\n<dl>{terms}</dl>`),
|
|
1533
|
+
).toHaveLength(0);
|
|
1534
|
+
expect(rule.check('src/ui/Doc.tsx', `// replacing the <dl> here would lose the terms`)).toHaveLength(0);
|
|
1535
|
+
});
|
|
1536
|
+
test('exempt by construction: PropertyList itself, DataTable, renderers, fixtures and stylesheets', () => {
|
|
1537
|
+
for (const file of ['src/react/components/PropertyList.tsx', 'src/react/components/DataTable/DataTable.tsx', 'src/react/markdown/Markdown.tsx', 'src/ui/Panel.spec.tsx', 'src/ui/panel.css']) {
|
|
1538
|
+
expect(rule.check(file, `<dl><dt>a</dt><dd>b</dd></dl>`)).toHaveLength(0);
|
|
1539
|
+
}
|
|
1540
|
+
});
|
|
1541
|
+
});
|
|
1542
|
+
|
|
1511
1543
|
describe('no-raw-nav', () => {
|
|
1512
1544
|
test('🔴 flags a hand-rolled <nav> — the header music shipped on 2026-09-25', () => {
|
|
1513
1545
|
expect(
|
|
@@ -71,6 +71,11 @@
|
|
|
71
71
|
// className="h-8">`, a Progress bar's `h-1.5` track) in some other file never
|
|
72
72
|
// false-positives.
|
|
73
73
|
//
|
|
74
|
+
// no-hand-rolled-property-list — FRM-1/FRM-2/LAY-5 (owner, 2026-10-05): no hand-rolled `<dl>` — a
|
|
75
|
+
// record's label/value facts are `cursedbelt/react/property-list`'s `PropertyList` +
|
|
76
|
+
// `Property`. On the ELEMENT, like `no-raw-data-table`. The belt's own PropertyList, the
|
|
77
|
+
// markdown/rich-text renderers and `*.spec|test.*` fixtures are exempt by construction.
|
|
78
|
+
//
|
|
74
79
|
// no-raw-data-table — DESIGN.md §3 "Visual Formatting & Data Presentation" (owner, 2026-08-14): no hand-rolled `<table>` —
|
|
75
80
|
// use `cursedbelt/react/data-table`'s `DataTable`, which carries sorting, a global
|
|
76
81
|
// filter, column visibility, pagination and the phone card tier. The rule is on the
|
|
@@ -1187,6 +1192,64 @@ const noRawDataTable: Rule = {
|
|
|
1187
1192
|
},
|
|
1188
1193
|
};
|
|
1189
1194
|
|
|
1195
|
+
// ── no-hand-rolled-property-list (FRM-1 / FRM-2 / LAY-5, 2026-10-05) ─────────────
|
|
1196
|
+
// The owner put desk's Components page under Linear's project page and asked a judge which
|
|
1197
|
+
// had the better UI. desk lost, and the facts of a record were a named reason: five
|
|
1198
|
+
// label/value rows, each in its own filled box with a bold label ("tightly boxed container
|
|
1199
|
+
// shapes, stark block borders"), where Linear's Properties panel is a muted label column, a
|
|
1200
|
+
// value column and nothing else. His instruction that day:
|
|
1201
|
+
//
|
|
1202
|
+
// *"be relentless in forcing our apps to no longer hand roll forms and layouts. They may
|
|
1203
|
+
// customize them somewhat but they must build off of standardized components."*
|
|
1204
|
+
//
|
|
1205
|
+
// desk's rows were a `<dl>` with a class vocabulary of its own, and a source comment beside it
|
|
1206
|
+
// records that an earlier version of the same pane shipped with NO rules behind its classes at
|
|
1207
|
+
// all — a browser-default definition list. That is what the raw element costs: every `<dl>` is
|
|
1208
|
+
// a fresh decision about the label column's width, the label's weight, the row's height and
|
|
1209
|
+
// whether a row gets a box, and each app decides differently.
|
|
1210
|
+
//
|
|
1211
|
+
// So the rule is on the ELEMENT, for the reason `no-raw-data-table` gives: "did this
|
|
1212
|
+
// hand-rolled list keep one label column and no boxes?" is not decidable by a scanner. A `<dl>`
|
|
1213
|
+
// in app source is a violation; the fix is `PropertyList` + `Property`
|
|
1214
|
+
// (`cursedbelt/react/property-list`), which takes a `className` for the customising an app is
|
|
1215
|
+
// allowed. `<dt>`/`<dd>` are not counted separately: they cannot appear without the `<dl>`.
|
|
1216
|
+
//
|
|
1217
|
+
// Exempt by construction: the belt's own `PropertyList`, `DataTable` (its phone card tier redraws
|
|
1218
|
+
// a ROW as pairs, which is the table's job), the markdown and rich-text renderers (a document
|
|
1219
|
+
// may CONTAIN a definition list), and `*.spec|test.*` fixtures.
|
|
1220
|
+
const RAW_DL_RE = /<dl\b/g;
|
|
1221
|
+
const RAW_DL_IGNORE_RE = /guardrails-ignore\s+no-hand-rolled-property-list\s*:/;
|
|
1222
|
+
// DataTable's two are the table's own phone card tier and footer summary: a row redrawn as pairs.
|
|
1223
|
+
const RAW_DL_EXEMPT_FRAGMENTS = ["react/components/PropertyList.tsx", "react/components/DataTable/", "react/markdown/", "react/rich-text/"];
|
|
1224
|
+
|
|
1225
|
+
const noHandRolledPropertyList: Rule = {
|
|
1226
|
+
name: "no-hand-rolled-property-list",
|
|
1227
|
+
description:
|
|
1228
|
+
"docs/design/forms-and-controls.md FRM-1/FRM-2, page-layout.md LAY-5: no hand-rolled <dl> — a record's label/value facts are cursedbelt's PropertyList + Property (cursedbelt/react/property-list): one shared label column, muted labels, no row boxes. A raw <dl> is how every app picks its own label width, weight and row fill.",
|
|
1229
|
+
check(filePath, source) {
|
|
1230
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
1231
|
+
if (extname(normalized) === ".css" || isTestSource(normalized)) return [];
|
|
1232
|
+
if (RAW_DL_EXEMPT_FRAGMENTS.some((fragment) => normalized.includes(fragment))) return [];
|
|
1233
|
+
const violations: Violation[] = [];
|
|
1234
|
+
const scannable = blankComments(source);
|
|
1235
|
+
const lines = source.split("\n");
|
|
1236
|
+
for (const match of scannable.matchAll(RAW_DL_RE)) {
|
|
1237
|
+
const line = lineOf(source, match.index ?? 0);
|
|
1238
|
+
// The contiguous comment block above, same as no-raw-data-table: a real reason runs
|
|
1239
|
+
// to several lines and its marker sits on the first.
|
|
1240
|
+
const block: string[] = [];
|
|
1241
|
+
for (let i = line - 2; i >= 0 && block.length < 12; i--) {
|
|
1242
|
+
const raw = lines[i] ?? "";
|
|
1243
|
+
if (raw.trim() === "") break;
|
|
1244
|
+
block.push(raw);
|
|
1245
|
+
}
|
|
1246
|
+
if (RAW_DL_IGNORE_RE.test(block.join("\n"))) continue;
|
|
1247
|
+
violations.push({ file: filePath, line, snippet: lines[line - 1]?.trim() ?? "" });
|
|
1248
|
+
}
|
|
1249
|
+
return violations;
|
|
1250
|
+
},
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1190
1253
|
// ── no-unguarded-editing-modal ──────────────────────────────────────────────
|
|
1191
1254
|
// The owner lost a half-typed password by clicking outside an editing modal,
|
|
1192
1255
|
// which dismissed it and threw the form away with no confirmation. `Modal`/
|
|
@@ -1211,7 +1274,9 @@ const noRawDataTable: Rule = {
|
|
|
1211
1274
|
//
|
|
1212
1275
|
// Opt-out, same shape as the other rules — on or within 3 lines above the offense:
|
|
1213
1276
|
// // guardrails-ignore no-unguarded-editing-modal: read-only preview, no form state
|
|
1214
|
-
|
|
1277
|
+
// `EditorPage` is the same dismissible surface drawn as a page — Escape and Back leave it, so a
|
|
1278
|
+
// half-made template is lost exactly as a half-typed modal was (desk-new-template-no-modal).
|
|
1279
|
+
const MODAL_TAG_OPEN_RE = /<(Modal|Drawer|EditorPage)\b/g;
|
|
1215
1280
|
/** Editing controls. A dialog containing one of these can hold unsaved input. */
|
|
1216
1281
|
const EDITING_CONTROL_RE =
|
|
1217
1282
|
/<(?:Field|Input|TextInput|PasswordInput|NumberInput|Textarea|TextArea|Select|MultiSelect|Combobox|Checkbox|Switch|Toggle|RadioGroup|Slider|DatePicker|ColorPicker|RichTextEditor|CodeEditor|TagInput|input|textarea|select)\b/;
|
|
@@ -2816,6 +2881,7 @@ export const RULES: Record<string, Rule> = {
|
|
|
2816
2881
|
[controlHeightTokens.name]: controlHeightTokens,
|
|
2817
2882
|
[noRawZindex.name]: noRawZindex,
|
|
2818
2883
|
[noRawDataTable.name]: noRawDataTable,
|
|
2884
|
+
[noHandRolledPropertyList.name]: noHandRolledPropertyList,
|
|
2819
2885
|
[noTailwindPalette.name]: noTailwindPalette,
|
|
2820
2886
|
[themeColoursResolve.name]: themeColoursResolve,
|
|
2821
2887
|
[noRawTitleAttr.name]: noRawTitleAttr,
|
package/scripts/styleAreas.ts
CHANGED
|
@@ -132,6 +132,8 @@ export const AREAS: readonly Area[] = [
|
|
|
132
132
|
'layout',
|
|
133
133
|
// The engagement beacon draws nothing; it is here because every app beacons (5.2.0).
|
|
134
134
|
'engagement',
|
|
135
|
+
// The line keys draw nothing either; every app installs them at boot.
|
|
136
|
+
'line-keys',
|
|
135
137
|
'namespace',
|
|
136
138
|
'portal',
|
|
137
139
|
'slots',
|
|
@@ -184,6 +186,7 @@ export const AREAS: readonly Area[] = [
|
|
|
184
186
|
'components/PathText.tsx',
|
|
185
187
|
'components/Popover.tsx',
|
|
186
188
|
'components/Progress.tsx',
|
|
189
|
+
'components/PropertyList.tsx',
|
|
187
190
|
'components/QueryBoundary.tsx',
|
|
188
191
|
'components/ScrollArea.tsx',
|
|
189
192
|
'components/Section.tsx',
|
|
@@ -50,10 +50,10 @@ export const FALLBACK_TOKENS: ChartTokens = {
|
|
|
50
50
|
series: FALLBACK_SERIES,
|
|
51
51
|
accent: '#34d399',
|
|
52
52
|
foreground: '#f9fafb',
|
|
53
|
-
foregroundMuted: '#
|
|
54
|
-
background: '#
|
|
55
|
-
surface: '#
|
|
56
|
-
border: '#
|
|
53
|
+
foregroundMuted: '#9b9ea7',
|
|
54
|
+
background: '#08090a',
|
|
55
|
+
surface: '#17181b',
|
|
56
|
+
border: '#31333a',
|
|
57
57
|
success: '#22c55e',
|
|
58
58
|
warning: '#f59e0b',
|
|
59
59
|
danger: '#ef4444',
|
|
@@ -25,6 +25,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
|
|
|
25
25
|
import { cn } from '../lib/cn.js';
|
|
26
26
|
import type { CodeLanguage } from './languages.js';
|
|
27
27
|
import { loadLanguageExtension } from './languages.js';
|
|
28
|
+
import { lineKeyBindings } from './lineAbove.js';
|
|
28
29
|
import { cbEditorTheme, cbSyntaxHighlighting } from './theme.js';
|
|
29
30
|
|
|
30
31
|
export interface CodeEditorProps {
|
|
@@ -124,7 +125,7 @@ export function CodeEditor({
|
|
|
124
125
|
const dynExtensions = useMemo<Extension[]>(() => {
|
|
125
126
|
const exts: Extension[] = [
|
|
126
127
|
indentUnit.of(' '.repeat(tabSize)),
|
|
127
|
-
keymap.of([...defaultKeymap, ...historyKeymap, indentWithTab]),
|
|
128
|
+
keymap.of([...lineKeyBindings, ...defaultKeymap, ...historyKeymap, indentWithTab]),
|
|
128
129
|
];
|
|
129
130
|
if (showLineNumbers) exts.push(lineNumbersExt());
|
|
130
131
|
if (showFoldGutter) exts.push(foldGutterExt());
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { EditorSelection, EditorState, type Transaction } from '@codemirror/state';
|
|
3
|
+
import { insertBlankLineAbove, lineKeyBindings } from './lineAbove.js';
|
|
4
|
+
|
|
5
|
+
function run(doc: string, at: number): { doc: string; caret: number } {
|
|
6
|
+
let state = EditorState.create({ doc, selection: EditorSelection.cursor(at) });
|
|
7
|
+
insertBlankLineAbove({ state, dispatch: (tr: Transaction) => { state = tr.state; } });
|
|
8
|
+
return { doc: state.doc.toString(), caret: state.selection.main.head };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('insertBlankLineAbove — ⇧⌘↵ in CodeEditor', () => {
|
|
12
|
+
test('opens a line above from anywhere on the line, with its indentation', () => {
|
|
13
|
+
expect(run('a\n bcd', 5)).toEqual({ doc: 'a\n \n bcd', caret: 4 });
|
|
14
|
+
expect(run('first', 3)).toEqual({ doc: '\nfirst', caret: 0 });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('a read-only editor is left alone', () => {
|
|
18
|
+
const state = EditorState.create({ doc: 'x', extensions: EditorState.readOnly.of(true) });
|
|
19
|
+
expect(insertBlankLineAbove({ state, dispatch: () => {} })).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('lineKeyBindings — ⌘↑ / ⌘↓ in CodeEditor', () => {
|
|
24
|
+
function press(key: string, doc: string, at: number): { doc: string; caret: number } {
|
|
25
|
+
let state = EditorState.create({ doc, selection: EditorSelection.cursor(at) });
|
|
26
|
+
const binding = lineKeyBindings.find((b) => b.key === key);
|
|
27
|
+
// moveLineUp/Down are StateCommands at heart; they read only `state` and `dispatch`.
|
|
28
|
+
binding?.run?.({ state, dispatch: (tr: Transaction) => { state = tr.state; } } as never);
|
|
29
|
+
return { doc: state.doc.toString(), caret: state.selection.main.head };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test('⌘↑ moves the line up and ⌘↓ moves it down, the caret riding with it', () => {
|
|
33
|
+
expect(press('Mod-ArrowUp', 'one\ntwo', 5)).toEqual({ doc: 'two\none', caret: 1 });
|
|
34
|
+
expect(press('Mod-ArrowDown', 'one\ntwo', 1)).toEqual({ doc: 'two\none', caret: 5 });
|
|
35
|
+
});
|
|
36
|
+
});
|