cursedbelt 5.26.0 → 5.28.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/IntervalTimeline.d.ts.map +1 -1
- package/dist/react/charts/IntervalTimeline.js +6 -2
- package/dist/react/charts/IntervalTimeline.js.map +1 -1
- package/dist/react/components/Button.d.ts +1 -1
- package/dist/react/components/Button.d.ts.map +1 -1
- package/dist/react/components/Button.js +4 -0
- package/dist/react/components/Button.js.map +1 -1
- package/dist/react/components/CodeArea.d.ts.map +1 -1
- package/dist/react/components/CodeArea.js +3 -1
- package/dist/react/components/CodeArea.js.map +1 -1
- package/dist/react/components/DataTable/DataTable.js +2 -2
- package/dist/react/components/DataTable/DataTable.js.map +1 -1
- package/dist/react/components/DataTable/TableFilterBuilder.d.ts.map +1 -1
- package/dist/react/components/DataTable/TableFilterBuilder.js +2 -1
- package/dist/react/components/DataTable/TableFilterBuilder.js.map +1 -1
- package/dist/react/components/FindingsList.d.ts.map +1 -1
- package/dist/react/components/FindingsList.js +2 -1
- package/dist/react/components/FindingsList.js.map +1 -1
- package/dist/react/components/InfoHint.d.ts.map +1 -1
- package/dist/react/components/InfoHint.js +3 -1
- package/dist/react/components/InfoHint.js.map +1 -1
- package/dist/react/components/Section.d.ts.map +1 -1
- package/dist/react/components/Section.js +3 -1
- package/dist/react/components/Section.js.map +1 -1
- package/dist/react/components/calendar/MonthGrid.d.ts.map +1 -1
- package/dist/react/components/calendar/MonthGrid.js +33 -32
- package/dist/react/components/calendar/MonthGrid.js.map +1 -1
- package/dist/react/components/calendar/WeekView.d.ts.map +1 -1
- package/dist/react/components/calendar/WeekView.js +2 -1
- package/dist/react/components/calendar/WeekView.js.map +1 -1
- package/dist/react/components/sharing/RecipientPicker.d.ts.map +1 -1
- package/dist/react/components/sharing/RecipientPicker.js +3 -1
- package/dist/react/components/sharing/RecipientPicker.js.map +1 -1
- package/dist/react/dashboard-grid/WidgetFrame.d.ts.map +1 -1
- package/dist/react/dashboard-grid/WidgetFrame.js +2 -1
- package/dist/react/dashboard-grid/WidgetFrame.js.map +1 -1
- package/dist/react/diff-viewer/TextComparePane.d.ts.map +1 -1
- package/dist/react/diff-viewer/TextComparePane.js +2 -1
- package/dist/react/diff-viewer/TextComparePane.js.map +1 -1
- package/dist/react/emoji/EmojiAutocomplete.d.ts.map +1 -1
- package/dist/react/emoji/EmojiAutocomplete.js +3 -1
- package/dist/react/emoji/EmojiAutocomplete.js.map +1 -1
- package/dist/react/folder-tree/FolderPicker.d.ts.map +1 -1
- package/dist/react/folder-tree/FolderPicker.js +3 -2
- package/dist/react/folder-tree/FolderPicker.js.map +1 -1
- package/dist/react/layoutEngine/LayoutCanvas.d.ts.map +1 -1
- package/dist/react/layoutEngine/LayoutCanvas.js.map +1 -1
- package/dist/react/layoutEngine/NodeInspector.d.ts.map +1 -1
- package/dist/react/layoutEngine/NodeInspector.js +2 -1
- package/dist/react/layoutEngine/NodeInspector.js.map +1 -1
- package/dist/react/media/TrimRangeSlider.d.ts.map +1 -1
- package/dist/react/media/TrimRangeSlider.js +3 -1
- package/dist/react/media/TrimRangeSlider.js.map +1 -1
- package/dist/react/media-gallery/MediaGallery.d.ts +14 -1
- package/dist/react/media-gallery/MediaGallery.d.ts.map +1 -1
- package/dist/react/media-gallery/MediaGallery.js +11 -7
- package/dist/react/media-gallery/MediaGallery.js.map +1 -1
- package/dist/react/media-gallery/index.d.ts +1 -1
- package/dist/react/media-gallery/index.d.ts.map +1 -1
- package/dist/react/media-gallery/index.js +1 -1
- package/dist/react/media-gallery/index.js.map +1 -1
- package/dist/react/problem-detail/ProblemDetailView.d.ts.map +1 -1
- package/dist/react/problem-detail/ProblemDetailView.js +2 -1
- package/dist/react/problem-detail/ProblemDetailView.js.map +1 -1
- package/dist/scripts/guardrailsEnforce.d.ts.map +1 -1
- package/dist/styles-areas/core.css +29 -28
- package/dist/styles-utilities.css +9 -0
- package/dist/theme.css +11 -0
- package/package.json +8 -2
- package/scripts/guardrails-baseline.json +2 -1
- package/scripts/guardrailsEnforce.spec.ts +36 -0
- package/scripts/guardrailsEnforce.ts +66 -0
- package/src/react/charts/IntervalTimeline.tsx +2 -0
- package/src/react/components/Button.spec.tsx +8 -0
- package/src/react/components/Button.tsx +4 -0
- package/src/react/components/CodeArea.tsx +1 -0
- package/src/react/components/DataTable/DataTable.tsx +4 -4
- package/src/react/components/DataTable/TableFilterBuilder.tsx +3 -2
- package/src/react/components/FindingsList.tsx +9 -5
- package/src/react/components/InfoHint.tsx +1 -0
- package/src/react/components/Section.tsx +1 -0
- package/src/react/components/calendar/MonthGrid.tsx +3 -1
- package/src/react/components/calendar/WeekView.tsx +3 -2
- package/src/react/components/sharing/RecipientPicker.tsx +4 -1
- package/src/react/dashboard-grid/WidgetFrame.tsx +4 -6
- package/src/react/diff-viewer/TextComparePane.tsx +4 -6
- package/src/react/emoji/EmojiAutocomplete.tsx +1 -0
- package/src/react/folder-tree/FolderPicker.tsx +5 -4
- package/src/react/layoutEngine/LayoutCanvas.tsx +1 -0
- package/src/react/layoutEngine/NodeInspector.tsx +3 -2
- package/src/react/media/TrimRangeSlider.tsx +1 -0
- package/src/react/media-gallery/MediaGallery.spec.tsx +22 -0
- package/src/react/media-gallery/MediaGallery.tsx +27 -6
- package/src/react/media-gallery/index.ts +2 -0
- package/src/react/problem-detail/ProblemDetailView.tsx +14 -5
- package/src/styles-areas/core.css +29 -28
- package/src/styles-utilities.css +9 -0
- package/src/theme.css +11 -0
|
@@ -16,6 +16,7 @@ import { EventChip } from './EventChip.js';
|
|
|
16
16
|
import { filterOccurrences } from './occurrences.js';
|
|
17
17
|
import type { CalendarEventBase, OccurrenceFilters } from './types.js';
|
|
18
18
|
import { TAP_TARGET_ALWAYS } from '../../lib/tapTarget.js';
|
|
19
|
+
import { Surface } from '../Surface.js';
|
|
19
20
|
|
|
20
21
|
export interface WeekViewProps {
|
|
21
22
|
/** Any date within the week to render. */
|
|
@@ -62,7 +63,7 @@ export function WeekView({
|
|
|
62
63
|
const labels = WEEKDAY_LABELS[weekStart];
|
|
63
64
|
|
|
64
65
|
return (
|
|
65
|
-
<
|
|
66
|
+
<Surface level="flat" pad="none" radius="xl" className={cn('overflow-hidden bg-card', className)}>
|
|
66
67
|
{/* Weekday header with date numbers */}
|
|
67
68
|
<div className='grid grid-cols-[repeat(7,minmax(0,1fr))] border-b border-border bg-muted/40'>
|
|
68
69
|
{days.map((dayKey, i) => (
|
|
@@ -133,6 +134,6 @@ export function WeekView({
|
|
|
133
134
|
);
|
|
134
135
|
})}
|
|
135
136
|
</div>
|
|
136
|
-
</
|
|
137
|
+
</Surface>
|
|
137
138
|
);
|
|
138
139
|
}
|
|
@@ -113,7 +113,10 @@ export function RecipientPicker({
|
|
|
113
113
|
</div>
|
|
114
114
|
|
|
115
115
|
{showDropdown && (
|
|
116
|
-
<div
|
|
116
|
+
<div
|
|
117
|
+
// guardrails-ignore no-hand-rolled-surface: the suggestions dropdown under the field — an overlay listbox (bg-popover, shadow-overlay), not a card
|
|
118
|
+
className='mt-1 overflow-hidden rounded-md border border-border bg-popover shadow-overlay'
|
|
119
|
+
>
|
|
117
120
|
<ScrollArea className='max-h-56'>
|
|
118
121
|
<div role='listbox' className='py-1'>
|
|
119
122
|
{visible.map((r) => {
|
|
@@ -3,6 +3,7 @@ import { DismissButton } from '../components/DismissButton.js';
|
|
|
3
3
|
import { ScrollArea } from '../components/ScrollArea.js';
|
|
4
4
|
import { WidgetHeader } from '../components/WidgetHeader.js';
|
|
5
5
|
import { cn } from '../lib/cn.js';
|
|
6
|
+
import { Surface } from '../components/Surface.js';
|
|
6
7
|
|
|
7
8
|
export interface WidgetFrameProps {
|
|
8
9
|
/** Widget title shown in the chrome header. */
|
|
@@ -59,11 +60,8 @@ export function WidgetFrame({
|
|
|
59
60
|
onRemove !== undefined;
|
|
60
61
|
|
|
61
62
|
return (
|
|
62
|
-
<
|
|
63
|
-
className={cn(
|
|
64
|
-
'flex h-full flex-col overflow-hidden rounded-md border border-border bg-surface',
|
|
65
|
-
className,
|
|
66
|
-
)}
|
|
63
|
+
<Surface
|
|
64
|
+
level="flat" pad="none" radius="md" className={cn('flex h-full flex-col overflow-hidden', className)}
|
|
67
65
|
>
|
|
68
66
|
{hasHeader && (
|
|
69
67
|
<WidgetHeader
|
|
@@ -101,6 +99,6 @@ export function WidgetFrame({
|
|
|
101
99
|
<ScrollArea axis='y' space='none' safeArea={false} className='p-3'>
|
|
102
100
|
{children}
|
|
103
101
|
</ScrollArea>
|
|
104
|
-
</
|
|
102
|
+
</Surface>
|
|
105
103
|
);
|
|
106
104
|
}
|
|
@@ -16,6 +16,7 @@ import { useMemo } from 'react';
|
|
|
16
16
|
import { cn } from '../lib/cn.js';
|
|
17
17
|
import { DiffViewer } from './DiffViewer.js';
|
|
18
18
|
import { diffStats } from './diff.js';
|
|
19
|
+
import { Surface } from '../components/Surface.js';
|
|
19
20
|
|
|
20
21
|
/** One text source in the comparison. */
|
|
21
22
|
export interface TextSource {
|
|
@@ -89,15 +90,12 @@ export function TextComparePane({
|
|
|
89
90
|
|
|
90
91
|
if (sources.length === 0) {
|
|
91
92
|
return (
|
|
92
|
-
<
|
|
93
|
-
className={cn(
|
|
94
|
-
'cb-text-compare rounded-md border border-border bg-muted p-4 text-center text-sm text-muted-foreground',
|
|
95
|
-
className,
|
|
96
|
-
)}
|
|
93
|
+
<Surface
|
|
94
|
+
level="flat" pad="none" radius="md" className={cn('cb-text-compare bg-muted p-4 text-center text-sm text-muted-foreground', className)}
|
|
97
95
|
style={style}
|
|
98
96
|
>
|
|
99
97
|
Nothing to compare.
|
|
100
|
-
</
|
|
98
|
+
</Surface>
|
|
101
99
|
);
|
|
102
100
|
}
|
|
103
101
|
|
|
@@ -111,6 +111,7 @@ export function EmojiAutocomplete({
|
|
|
111
111
|
return (
|
|
112
112
|
<FloatingPortal>
|
|
113
113
|
<div
|
|
114
|
+
// guardrails-ignore no-hand-rolled-surface: a floating-ui popup panel, positioned by its own `floatingStyles`; Popover owns its own frame the same way
|
|
114
115
|
ref={refs.setFloating}
|
|
115
116
|
style={floatingStyles}
|
|
116
117
|
// floating-ui's own positioning `style` (set above) is required on this exact
|
|
@@ -18,6 +18,7 @@ import { Input } from "../components/Input.js";
|
|
|
18
18
|
import { FolderBreadcrumb } from "./FolderBreadcrumb.js";
|
|
19
19
|
import { formatFolderPath, isValidFolderName, normalizeFolderName } from "cursedbelt-core/folder-tree/path";
|
|
20
20
|
import { type FolderNode, flattenFolderTree } from "cursedbelt-core/folder-tree/tree";
|
|
21
|
+
import { Surface } from "../components/Surface.js";
|
|
21
22
|
|
|
22
23
|
export interface FolderPickerProps {
|
|
23
24
|
/** The current derived tree (from the items' folder paths). */
|
|
@@ -111,7 +112,7 @@ export function FolderPicker({
|
|
|
111
112
|
</button>
|
|
112
113
|
|
|
113
114
|
{open && (
|
|
114
|
-
<
|
|
115
|
+
<Surface level="flat" pad="none" radius="md" className="flex flex-col gap-1 bg-[var(--card)] p-1.5">
|
|
115
116
|
{allowNone && (
|
|
116
117
|
<button
|
|
117
118
|
// guardrails-ignore no-raw-action-button: option selection, no data mutation
|
|
@@ -165,7 +166,7 @@ export function FolderPicker({
|
|
|
165
166
|
+ New folder…
|
|
166
167
|
</Button>
|
|
167
168
|
) : (
|
|
168
|
-
<
|
|
169
|
+
<Surface level="flat" pad="none" radius="md" className="flex flex-col gap-1 bg-[var(--muted)]/10 p-2">
|
|
169
170
|
<span className="text-xs text-[var(--muted-foreground)]">
|
|
170
171
|
New folder in{" "}
|
|
171
172
|
{creatingUnder.length === 0 ? (
|
|
@@ -210,9 +211,9 @@ export function FolderPicker({
|
|
|
210
211
|
Cancel
|
|
211
212
|
</Button>
|
|
212
213
|
</div>
|
|
213
|
-
</
|
|
214
|
+
</Surface>
|
|
214
215
|
)}
|
|
215
|
-
</
|
|
216
|
+
</Surface>
|
|
216
217
|
)}
|
|
217
218
|
</div>
|
|
218
219
|
);
|
|
@@ -421,6 +421,7 @@ export function LayoutCanvas<F extends LayoutField = LayoutField>({
|
|
|
421
421
|
</RootZone>
|
|
422
422
|
{activeNode && pointer && (
|
|
423
423
|
<div
|
|
424
|
+
// guardrails-ignore no-hand-rolled-surface: the drag ghost follows the pointer (position: fixed) — a transient cursor companion, not a container
|
|
424
425
|
aria-hidden
|
|
425
426
|
style={{
|
|
426
427
|
position: 'fixed',
|
|
@@ -18,6 +18,7 @@ import { cn } from '../lib/cn.js';
|
|
|
18
18
|
import type { BindingResolver } from './LayoutRenderer.js';
|
|
19
19
|
import { compatibleWidgets, widgetDef } from './registry.js';
|
|
20
20
|
import type { WidgetRegistry } from './types.js';
|
|
21
|
+
import { Surface } from '../components/Surface.js';
|
|
21
22
|
|
|
22
23
|
// The property panel for the selected node — rebuilt for NON-TECHNICAL editing. The
|
|
23
24
|
// primary layer answers "what should this block look like?" in plain language: pick a
|
|
@@ -177,7 +178,7 @@ function Preview<F extends LayoutField>({
|
|
|
177
178
|
resolved.kind === 'column' || resolved.kind === 'aggregate' ? resolved.field : undefined;
|
|
178
179
|
return (
|
|
179
180
|
<Labeled label='Preview'>
|
|
180
|
-
<
|
|
181
|
+
<Surface level="flat" pad="sm" radius="md" className='min-w-0 break-words text-sm text-foreground'>
|
|
181
182
|
{def?.render({
|
|
182
183
|
node,
|
|
183
184
|
resolved,
|
|
@@ -187,7 +188,7 @@ function Preview<F extends LayoutField>({
|
|
|
187
188
|
row: sampleRow,
|
|
188
189
|
rows: sampleRows,
|
|
189
190
|
}) ?? <span className='text-muted-foreground'>No preview</span>}
|
|
190
|
-
</
|
|
191
|
+
</Surface>
|
|
191
192
|
</Labeled>
|
|
192
193
|
);
|
|
193
194
|
}
|
|
@@ -92,6 +92,7 @@ export function TrimRangeSlider({
|
|
|
92
92
|
return (
|
|
93
93
|
<div className='select-none' data-cb-trim-slider>
|
|
94
94
|
<div
|
|
95
|
+
// guardrails-ignore no-hand-rolled-surface: the trim slider's TRACK — a pointer-driven control, not a container
|
|
95
96
|
ref={trackRef}
|
|
96
97
|
className='relative h-16 w-full cursor-pointer overflow-hidden rounded-md border border-border bg-muted'
|
|
97
98
|
onPointerMove={onPointerMove}
|
|
@@ -474,6 +474,28 @@ describe('MediaGallery navigation and orientation', () => {
|
|
|
474
474
|
fireEvent.click(second.getByRole('button', { name: /Pinned/ }));
|
|
475
475
|
expect(second.container.querySelector('.cbgd-watch-sticky')).toBeNull();
|
|
476
476
|
});
|
|
477
|
+
|
|
478
|
+
test('🔴 `pinStage` hands the pin to the APP — its own chrome pins it, and the titlebar button asks', () => {
|
|
479
|
+
const asked: boolean[] = [];
|
|
480
|
+
function App({ pinned }: { pinned: boolean }) {
|
|
481
|
+
return <MediaGallery items={ITEMS} pinStage={pinned} onPinStageChange={(next) => asked.push(next)} />;
|
|
482
|
+
}
|
|
483
|
+
const { container, getByRole, rerender } = render(<App pinned={false} />);
|
|
484
|
+
fireEvent.click(getByRole('button', { name: 'Open clip.mp4' }));
|
|
485
|
+
expect(container.querySelector('.cbgd-watch-sticky')).toBeNull();
|
|
486
|
+
|
|
487
|
+
// The app's own control (collections' rail) flips the prop — the stage pins with no click inside.
|
|
488
|
+
rerender(<App pinned={true} />);
|
|
489
|
+
expect(container.querySelector('.cbgd-watch-sticky')).not.toBeNull();
|
|
490
|
+
expect(getByRole('button', { name: /Pinned/ }).getAttribute('aria-pressed')).toBe('true');
|
|
491
|
+
|
|
492
|
+
// The titlebar button is now a REQUEST: it asks, and until the app answers nothing moves.
|
|
493
|
+
fireEvent.click(getByRole('button', { name: /Pinned/ }));
|
|
494
|
+
expect(asked).toEqual([false]);
|
|
495
|
+
expect(container.querySelector('.cbgd-watch-sticky')).not.toBeNull();
|
|
496
|
+
// Controlled does not move the remembering — the gallery still wrote the shared key.
|
|
497
|
+
expect(globalThis.localStorage.getItem('cbmg:pin-stage')).toBe('0');
|
|
498
|
+
});
|
|
477
499
|
});
|
|
478
500
|
|
|
479
501
|
/**
|
|
@@ -1555,6 +1555,19 @@ export interface MediaGalleryProps {
|
|
|
1555
1555
|
* detail view from its own chrome — see the note by `selectedId` in the body.
|
|
1556
1556
|
*/
|
|
1557
1557
|
selectedId?: string | null;
|
|
1558
|
+
/**
|
|
1559
|
+
* Whether the stage is PINNED to the top while the related grid scrolls under it, when the
|
|
1560
|
+
* consumer owns that — the titlebar's pin button then asks through `onPinStageChange`.
|
|
1561
|
+
*
|
|
1562
|
+
* The case that needed it (collections, 2026-09-29): *"add a … sidebar icon button to press
|
|
1563
|
+
* that toggles locking the media player at the top of the screen"* — the app's own chrome
|
|
1564
|
+
* has to reach a switch the gallery used to hold privately. Same shape as `viewMode`, and
|
|
1565
|
+
* 🔴 the same rule: controlled does NOT move the remembering. The gallery writes
|
|
1566
|
+
* `readPinStage`'s key when its own button is pressed, in both modes; an app toggling it from
|
|
1567
|
+
* its own chrome calls `writePinStage` itself.
|
|
1568
|
+
*/
|
|
1569
|
+
pinStage?: boolean;
|
|
1570
|
+
onPinStageChange?: (pinned: boolean) => void;
|
|
1558
1571
|
/** Custom byte loader for the pdf/sheet/text/html stages (default: `fetch(item.url)`). */
|
|
1559
1572
|
loadBytes?: (item: MediaGalleryItem) => Promise<ArrayBuffer>;
|
|
1560
1573
|
/** Shown when there are no items and not loading. */
|
|
@@ -1833,6 +1846,8 @@ export function MediaGallery({
|
|
|
1833
1846
|
onQueryChange,
|
|
1834
1847
|
onSelectedChange,
|
|
1835
1848
|
selectedId: controlledSelectedId,
|
|
1849
|
+
pinStage: controlledPinStage,
|
|
1850
|
+
onPinStageChange,
|
|
1836
1851
|
loadBytes,
|
|
1837
1852
|
empty,
|
|
1838
1853
|
emptyAction,
|
|
@@ -1888,7 +1903,17 @@ export function MediaGallery({
|
|
|
1888
1903
|
// What happens when a video ends — remembered across visits. See playbackPreferences.ts.
|
|
1889
1904
|
const [playbackEnd, setPlaybackEnd] = useState<PlaybackEndBehavior>(readPlaybackEnd);
|
|
1890
1905
|
// Whether the stage stays pinned at the top while the related grid scrolls under it.
|
|
1891
|
-
|
|
1906
|
+
// Controlled when the consumer passes `pinStage` — see the prop.
|
|
1907
|
+
const [uncontrolledPinStage, setUncontrolledPinStage] = useState<boolean>(readPinStage);
|
|
1908
|
+
const pinStage = controlledPinStage !== undefined ? controlledPinStage : uncontrolledPinStage;
|
|
1909
|
+
const setPinStage = useCallback(
|
|
1910
|
+
(next: boolean): void => {
|
|
1911
|
+
if (controlledPinStage === undefined) setUncontrolledPinStage(next);
|
|
1912
|
+
writePinStage(next);
|
|
1913
|
+
onPinStageChange?.(next);
|
|
1914
|
+
},
|
|
1915
|
+
[controlledPinStage, onPinStageChange],
|
|
1916
|
+
);
|
|
1892
1917
|
/*
|
|
1893
1918
|
* 🔴 The shell's `⤢ Fullscreen` lives HERE rather than inside `DenseGalleryDetail`,
|
|
1894
1919
|
* because the video stage has to know about it.
|
|
@@ -2712,11 +2737,7 @@ export function MediaGallery({
|
|
|
2712
2737
|
className={`cbgd-order-btn is-icon${pinStage ? ' is-active' : ''}`}
|
|
2713
2738
|
aria-pressed={pinStage}
|
|
2714
2739
|
aria-label={pinStage ? 'Pinned — unpin the stage' : 'Pin the stage'}
|
|
2715
|
-
onClick={() =>
|
|
2716
|
-
const next = !pinStage;
|
|
2717
|
-
setPinStage(next);
|
|
2718
|
-
writePinStage(next);
|
|
2719
|
-
}}
|
|
2740
|
+
onClick={() => setPinStage(!pinStage)}
|
|
2720
2741
|
>
|
|
2721
2742
|
<Pin aria-hidden />
|
|
2722
2743
|
</button>
|
|
@@ -50,11 +50,13 @@ export {
|
|
|
50
50
|
type PlaybackEndBehavior,
|
|
51
51
|
nextPlaybackEnd,
|
|
52
52
|
readImageDwell,
|
|
53
|
+
readPinStage,
|
|
53
54
|
readPlaybackEnd,
|
|
54
55
|
type GalleryViewMode,
|
|
55
56
|
readThumbAutoplay,
|
|
56
57
|
readViewMode,
|
|
57
58
|
writeImageDwell,
|
|
59
|
+
writePinStage,
|
|
58
60
|
writePlaybackEnd,
|
|
59
61
|
writeThumbAutoplay,
|
|
60
62
|
writeViewMode,
|
|
@@ -5,6 +5,7 @@ import { Badge } from '../components/Badge.js';
|
|
|
5
5
|
import { CopyButton } from '../components/CopyButton.js';
|
|
6
6
|
import { PathText } from '../components/PathText.js';
|
|
7
7
|
import { cn } from '../lib/cn.js';
|
|
8
|
+
import { Surface } from '../components/Surface.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* A reusable, presentational "why is this problem happening" drill-down.
|
|
@@ -153,13 +154,13 @@ export function ProblemDetailView({
|
|
|
153
154
|
<section className='grid gap-2 sm:grid-cols-2'>
|
|
154
155
|
{EXPLAIN_ROWS.map(({ key, label, icon }) =>
|
|
155
156
|
explain[key] ? (
|
|
156
|
-
<
|
|
157
|
+
<Surface key={key} level="flat" pad="sm" radius="lg" className='bg-card'>
|
|
157
158
|
<div className='mb-1 flex items-center gap-1.5 text-muted-foreground'>
|
|
158
159
|
<span className='shrink-0'>{icon}</span>
|
|
159
160
|
<span className='text-caption font-semibold uppercase tracking-wide'>{label}</span>
|
|
160
161
|
</div>
|
|
161
162
|
<p className='leading-snug text-foreground'>{explain[key]}</p>
|
|
162
|
-
</
|
|
163
|
+
</Surface>
|
|
163
164
|
) : null,
|
|
164
165
|
)}
|
|
165
166
|
</section>
|
|
@@ -208,10 +209,15 @@ export function ProblemDetailView({
|
|
|
208
209
|
</div>
|
|
209
210
|
<ul className='flex flex-col gap-1'>
|
|
210
211
|
{ready.slice(0, 12).map((t) => (
|
|
211
|
-
<
|
|
212
|
+
<Surface
|
|
212
213
|
key={t.file}
|
|
213
|
-
|
|
214
|
+
asChild
|
|
215
|
+
level='flat'
|
|
216
|
+
pad='none'
|
|
217
|
+
radius='md'
|
|
218
|
+
className='flex items-center gap-2 rounded border-border/60 bg-card/50 px-2 py-1'
|
|
214
219
|
>
|
|
220
|
+
<li>
|
|
215
221
|
<span className='min-w-0 flex-1 truncate font-mono text-caption text-foreground'>
|
|
216
222
|
{t.file}
|
|
217
223
|
</span>
|
|
@@ -221,6 +227,7 @@ export function ProblemDetailView({
|
|
|
221
227
|
</Badge>
|
|
222
228
|
)}
|
|
223
229
|
</li>
|
|
230
|
+
</Surface>
|
|
224
231
|
))}
|
|
225
232
|
</ul>
|
|
226
233
|
{ready.length > 12 && (
|
|
@@ -268,7 +275,8 @@ export function ProblemDetailView({
|
|
|
268
275
|
)}
|
|
269
276
|
|
|
270
277
|
{triage && (
|
|
271
|
-
<
|
|
278
|
+
<Surface asChild level='flat' pad='sm' radius='lg' className='bg-muted/30 text-xs'>
|
|
279
|
+
<section>
|
|
272
280
|
<span className='text-caption font-semibold uppercase tracking-wide text-muted-foreground'>
|
|
273
281
|
System verdict · {triage.status}
|
|
274
282
|
</span>
|
|
@@ -278,6 +286,7 @@ export function ProblemDetailView({
|
|
|
278
286
|
<p className='mt-1 text-caption text-muted-foreground'>decided by {triage.decidedBy}</p>
|
|
279
287
|
)}
|
|
280
288
|
</section>
|
|
289
|
+
</Surface>
|
|
281
290
|
)}
|
|
282
291
|
|
|
283
292
|
{detail?.trim() && (
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* after an app's responsive variant (task 2124). An app that imports every area gets exactly
|
|
16
16
|
* what `cursedbelt/styles-utilities.css` emits — proved rule by rule in
|
|
17
17
|
* src/stylesAreas.spec.ts, which is also what fails when this file is stale.
|
|
18
|
-
*
|
|
18
|
+
* 718 candidate(s).
|
|
19
19
|
*
|
|
20
20
|
* Written by scripts/generateAreaStyles.ts (`bun run styles:areas`), which
|
|
21
21
|
* `bun run build` runs. The map of what is in which area, and why the areas are
|
|
@@ -43,21 +43,21 @@
|
|
|
43
43
|
@source inline("bg-background bg-background/80 bg-background/90 bg-background/95 bg-black/50 bg-border bg-card");
|
|
44
44
|
@source inline("bg-current bg-destructive bg-destructive-solid bg-destructive/15 bg-destructive/5");
|
|
45
45
|
@source inline("bg-destructive/8 bg-emerald-100 bg-field-bg bg-muted bg-muted-foreground/40");
|
|
46
|
-
@source inline("bg-muted-foreground/50 bg-muted/20 bg-popover bg-success bg-success
|
|
47
|
-
@source inline("bg-surface-sunken bg-tone-amber-bg bg-tone-cyan-bg bg-tone-emerald-bg
|
|
48
|
-
@source inline("bg-tone-
|
|
49
|
-
@source inline("bg-tone-slate-bg bg-tone-teal-bg bg-tone-violet-bg bg-transparent bg-warning
|
|
50
|
-
@source inline("bg-warning/5 bg-warning/8 bg-white block blur border border-accent/40
|
|
51
|
-
@source inline("border-b-2 border-b-[7px] border-b-current border-border
|
|
52
|
-
@source inline("border-
|
|
53
|
-
@source inline("border-
|
|
54
|
-
@source inline("border-tone-sky-fg/30 border-tone-slate-fg/30
|
|
55
|
-
@source inline("border-warning/40 border-warning/50 border-x-4
|
|
56
|
-
@source inline("break-all break-words c ca cap capitalize co com
|
|
57
|
-
@source inline("cursor-col-resize cursor-default cursor-ew-resize
|
|
58
|
-
@source inline("dark:bg-card-overlay dark:bg-card-raised
|
|
59
|
-
@source inline("data-[disabled]:
|
|
60
|
-
@source inline("data-[interactive=true]:duration-[var(--duration-base)]");
|
|
46
|
+
@source inline("bg-muted-foreground/50 bg-muted/20 bg-popover bg-success bg-success-solid bg-success/15");
|
|
47
|
+
@source inline("bg-success/5 bg-surface bg-surface-sunken bg-tone-amber-bg bg-tone-cyan-bg bg-tone-emerald-bg");
|
|
48
|
+
@source inline("bg-tone-fuchsia-bg bg-tone-lime-bg bg-tone-orange-bg bg-tone-purple-bg bg-tone-rose-bg");
|
|
49
|
+
@source inline("bg-tone-sky-bg bg-tone-slate-bg bg-tone-teal-bg bg-tone-violet-bg bg-transparent bg-warning");
|
|
50
|
+
@source inline("bg-warning/15 bg-warning/5 bg-warning/8 bg-white block blur border border-accent/40");
|
|
51
|
+
@source inline("border-accent/50 border-b border-b-2 border-b-[7px] border-b-current border-border");
|
|
52
|
+
@source inline("border-border-strong border-border/60 border-collapse border-current border-destructive/30");
|
|
53
|
+
@source inline("border-destructive/40 border-destructive/50 border-field-border border-success/30");
|
|
54
|
+
@source inline("border-success/40 border-success/50 border-t border-tone-sky-fg/30 border-tone-slate-fg/30");
|
|
55
|
+
@source inline("border-transparent border-warning/30 border-warning/40 border-warning/50 border-x-4");
|
|
56
|
+
@source inline("border-x-transparent bot bottom bottom-0 break break-all break-words c ca cap capitalize co com");
|
|
57
|
+
@source inline("contain container content contents cursor cursor-col-resize cursor-default cursor-ew-resize");
|
|
58
|
+
@source inline("cursor-pointer cursor-row-resize d dark dark:bg-card-overlay dark:bg-card-raised");
|
|
59
|
+
@source inline("dark:bg-gray-800 data data-[disabled]:hover:bg-border data-[disabled]:opacity-50");
|
|
60
|
+
@source inline("data-[disabled]:pointer-events-none data-[interactive=true]:duration-[var(--duration-base)]");
|
|
61
61
|
@source inline("data-[interactive=true]:ease-[var(--ease-out)] data-[interactive=true]:hover:-translate-y-0.5");
|
|
62
62
|
@source inline("data-[interactive=true]:hover:border-border-strong data-[interactive=true]:hover:shadow-overlay");
|
|
63
63
|
@source inline("data-[interactive=true]:transition-all data-[placeholder]:text-muted-foreground");
|
|
@@ -86,11 +86,11 @@
|
|
|
86
86
|
@source inline("focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent");
|
|
87
87
|
@source inline("focus-visible:ring-accent-solid focus-visible:ring-destructive focus-visible:ring-field-ring/30");
|
|
88
88
|
@source inline("focus-visible:ring-offset-1 focus-visible:ring-offset-2 focus-visible:ring-offset-background");
|
|
89
|
-
@source inline("focus-visible:ring-ring focus-visible:text-destructive-foreground
|
|
90
|
-
@source inline("focus:bg-destructive/10 focus:outline-none focus:ring-2 focus:ring-accent
|
|
91
|
-
@source inline("focus:text-destructive font-bold font-medium font-mono font-normal
|
|
92
|
-
@source inline("gap-0.5 gap-1 gap-1.5 gap-12 gap-16 gap-2 gap-3 gap-4 gap-6 gap-8
|
|
93
|
-
@source inline("gap-y-2 gap-y-4 grid grid-cols-2");
|
|
89
|
+
@source inline("focus-visible:ring-ring focus-visible:ring-success focus-visible:text-destructive-foreground");
|
|
90
|
+
@source inline("focus:bg-accent/10 focus:bg-destructive/10 focus:outline-none focus:ring-2 focus:ring-accent");
|
|
91
|
+
@source inline("focus:text-accent focus:text-destructive font-bold font-medium font-mono font-normal");
|
|
92
|
+
@source inline("font-semibold gap gap-0 gap-0.5 gap-1 gap-1.5 gap-12 gap-16 gap-2 gap-3 gap-4 gap-6 gap-8");
|
|
93
|
+
@source inline("gap-x-2.5 gap-x-3 gap-y-1 gap-y-2 gap-y-4 grid grid-cols-2");
|
|
94
94
|
@source inline("grid-cols-[repeat(auto-fit,minmax(min(var(--cb-grid-min),100%),1fr))] group");
|
|
95
95
|
@source inline("group-active/sep:opacity-100 group-data-[state=open]:rotate-180");
|
|
96
96
|
@source inline("group-focus-visible/sep:opacity-100 group-hover group-hover/path:opacity-100");
|
|
@@ -99,13 +99,14 @@
|
|
|
99
99
|
@source inline("h-[var(--control-h-lg)] h-[var(--control-h-md)] h-[var(--control-h-sm)] h-auto h-full h-px hi");
|
|
100
100
|
@source inline("hid hidden hover hover:bg-accent hover:bg-accent-solid-hover hover:bg-accent/10");
|
|
101
101
|
@source inline("hover:bg-accent/20 hover:bg-destructive-solid hover:bg-destructive-solid-hover hover:bg-muted");
|
|
102
|
-
@source inline("hover:bg-
|
|
103
|
-
@source inline("hover:
|
|
104
|
-
@source inline("hover:underline i in inline inline-block
|
|
105
|
-
@source inline("is iso isolate it item items
|
|
106
|
-
@source inline("
|
|
107
|
-
@source inline("left left-1/2 left-2 lg lg:px-8
|
|
108
|
-
@source inline("max-h-[
|
|
102
|
+
@source inline("hover:bg-success-solid-hover hover:bg-transparent hover:border-accent");
|
|
103
|
+
@source inline("hover:border-destructive-solid hover:opacity-70 hover:text-accent hover:text-destructive");
|
|
104
|
+
@source inline("hover:text-destructive-foreground hover:text-foreground hover:underline i in inline inline-block");
|
|
105
|
+
@source inline("inline-flex inset inset-0 inset-x-0 inset-y-0 invisible is iso isolate it item items");
|
|
106
|
+
@source inline("items-baseline items-center items-end items-start just justify-between justify-center");
|
|
107
|
+
@source inline("justify-end l leading leading-none leading-relaxed leading-snug left left-1/2 left-2 lg lg:px-8");
|
|
108
|
+
@source inline("lower lowercase m m-0 max max-h max-h-[80vh] max-h-[calc(100dvh-2rem)]");
|
|
109
|
+
@source inline("max-h-[calc(var(--radix-popper-available-height,100dvh)-1rem)]");
|
|
109
110
|
@source inline("max-h-[min(90dvh,calc(100dvh-2rem))] max-h-[var(--radix-select-content-available-height)]");
|
|
110
111
|
@source inline("max-w-2xl max-w-4xl max-w-5xl max-w-[10rem] max-w-[calc(100vw-2rem)]");
|
|
111
112
|
@source inline("max-w-[var(--reading-width)] max-w-full max-w-lg max-w-sm max-w-xl max-w-xs mb-0.5 mb-1 md");
|
package/src/styles-utilities.css
CHANGED
|
@@ -2098,6 +2098,9 @@
|
|
|
2098
2098
|
.bg-success {
|
|
2099
2099
|
background-color: var(--success);
|
|
2100
2100
|
}
|
|
2101
|
+
.bg-success-solid {
|
|
2102
|
+
background-color: var(--success-solid);
|
|
2103
|
+
}
|
|
2101
2104
|
.bg-success\/5 {
|
|
2102
2105
|
background-color: var(--success);
|
|
2103
2106
|
@supports (color: color-mix(in lab, red, red)) {
|
|
@@ -3520,6 +3523,9 @@
|
|
|
3520
3523
|
background-color: color-mix(in oklab, var(--muted) 80%, transparent);
|
|
3521
3524
|
}
|
|
3522
3525
|
}
|
|
3526
|
+
.hover\:bg-success-solid-hover:hover {
|
|
3527
|
+
background-color: var(--success-solid-hover);
|
|
3528
|
+
}
|
|
3523
3529
|
.hover\:bg-transparent:hover {
|
|
3524
3530
|
background-color: transparent;
|
|
3525
3531
|
}
|
|
@@ -3667,6 +3673,9 @@
|
|
|
3667
3673
|
.focus-visible\:ring-ring:focus-visible {
|
|
3668
3674
|
--tw-ring-color: var(--ring);
|
|
3669
3675
|
}
|
|
3676
|
+
.focus-visible\:ring-success:focus-visible {
|
|
3677
|
+
--tw-ring-color: var(--success);
|
|
3678
|
+
}
|
|
3670
3679
|
.focus-visible\:ring-offset-1:focus-visible {
|
|
3671
3680
|
--tw-ring-offset-width: 1px;
|
|
3672
3681
|
--tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
package/src/theme.css
CHANGED
|
@@ -169,6 +169,13 @@
|
|
|
169
169
|
--destructive-solid: var(--destructive); /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
170
170
|
--destructive-solid-hover: #991b1b; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
171
171
|
--success: #16a34a; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
172
|
+
/* Solid-fill success, for a green button carrying WHITE text — the `destructive-solid`
|
|
173
|
+
split, for the same reason: --success is tuned as a status colour, and white on it
|
|
174
|
+
is 3.3:1. green-700 is 5.02:1 with white and its hover 7.1:1, in both modes, so it
|
|
175
|
+
is mode-stable like --accent-solid. First consumer: station's Resume, the green half
|
|
176
|
+
of a run/pause toggle whose Pause is `destructive-solid` (owner, 2026-09-29). */
|
|
177
|
+
--success-solid: #15803d; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
178
|
+
--success-solid-hover: #166534; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
172
179
|
--warning: #d97706; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
173
180
|
/* The gold a filled star is painted in (StarRating). Light needs a DARKER gold
|
|
174
181
|
than dark mode does, and this is measured, not taste: the neon below scores
|
|
@@ -470,6 +477,8 @@
|
|
|
470
477
|
--destructive-solid: #dc2626; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
471
478
|
--destructive-solid-hover: #b91c1c; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
472
479
|
--success: #22c55e; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
480
|
+
--success-solid: #15803d; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
481
|
+
--success-solid-hover: #166534; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
473
482
|
--warning: #f59e0b; /* guardrails-ignore no-hardcoded-colors: this file IS the theme token source of truth — every value here is necessarily a literal color definition */
|
|
474
483
|
/* The neon the star always had — correct against a near-black canvas at 10.76:1,
|
|
475
484
|
and the half of the pair that never needed changing. See the note at :root. */
|
|
@@ -845,6 +854,8 @@ option:disabled {
|
|
|
845
854
|
--color-destructive-solid: var(--destructive-solid);
|
|
846
855
|
--color-destructive-solid-hover: var(--destructive-solid-hover);
|
|
847
856
|
--color-success: var(--success);
|
|
857
|
+
--color-success-solid: var(--success-solid);
|
|
858
|
+
--color-success-solid-hover: var(--success-solid-hover);
|
|
848
859
|
--color-warning: var(--warning);
|
|
849
860
|
--color-star: var(--star);
|
|
850
861
|
/* AA-safe status TEXT over the 15% tinted badge fills */
|