paneltir 0.6.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.
@@ -0,0 +1,1489 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * What version of the kit a project is actually running.
5
+ *
6
+ * A dashboard that cannot say which Paneltir it is built on is a dashboard
7
+ * nobody can support: "it looks wrong" and "it is three versions behind" are
8
+ * indistinguishable from the outside. Both values are injected at build time
9
+ * from `package.json` and `fingerprint.json`, so they cannot be restated by
10
+ * hand and cannot drift.
11
+ */
12
+ /** The released version, matching the `vX.Y.Z` tag consumers install. */
13
+ declare const PANELTIR_VERSION: string;
14
+ /** SHA-256 over every tracked file at that version. */
15
+ declare const PANELTIR_FINGERPRINT: string;
16
+ /** How many files that fingerprint covers. */
17
+ declare const PANELTIR_FILE_COUNT: number;
18
+ /**
19
+ * Everything needed to report the kit in a panel's own footer or settings
20
+ * sheet, and to check it against the released version without opening
21
+ * node_modules.
22
+ */
23
+ declare const paneltirBuild: {
24
+ readonly version: string;
25
+ readonly fingerprint: string;
26
+ readonly fileCount: number;
27
+ /** The tag this build came from, ready to compare or to link. */
28
+ readonly tag: `v${string}`;
29
+ /** Short form, for a footer that has one line to spare. */
30
+ readonly short: `v${string} \u00B7 ${string}`;
31
+ };
32
+
33
+ /**
34
+ * Colour roles every project maps to its own palette.
35
+ *
36
+ * The library ships no defaults on purpose: when a project leaves a token
37
+ * out, the component that uses it renders without colour, so the omission is
38
+ * visible immediately instead of inheriting a generic colour nobody asked for.
39
+ */
40
+ interface DashboardThemeTokens {
41
+ /** Page background. */
42
+ ground: string;
43
+ /** First stop of the top gradient, and the background of the detail sheet. */
44
+ groundRaised: string;
45
+ /** Columns, tiles, chips, form fields. */
46
+ panel: string;
47
+ /** Cards, resting on top of "panel". */
48
+ raised: string;
49
+ /** A real border (alpha around 0.13 over white in dark themes). */
50
+ line: string;
51
+ /** A faint divider (alpha around 0.07 over white in dark themes). */
52
+ lineFaint: string;
53
+ /** Content text, full opacity. */
54
+ ink: string;
55
+ /** Secondary text (about 0.62 of ink). */
56
+ inkDim: string;
57
+ /** Labels and placeholders (about 0.34 of ink). */
58
+ inkFaint: string;
59
+ /** The single accent: active chip, primary button, focus ring. */
60
+ accent: string;
61
+ /** First stop of the primary button gradient. Defaults to accent. */
62
+ accentStart?: string;
63
+ /** Second stop of the primary button gradient. Defaults to accent. */
64
+ accentEnd?: string;
65
+ /** Text colour on top of the accent — usually the same value as "ground". */
66
+ accentInk?: string;
67
+ danger: string;
68
+ warning: string;
69
+ success: string;
70
+ neutral: string;
71
+ }
72
+
73
+ /**
74
+ * Shape roles, the counterpart to the colour roles in `tokens.ts`.
75
+ *
76
+ * A theme that only swaps colour produces the same panel in a different
77
+ * palette, which is fine for a preset but not for an identity: Old Money and
78
+ * Midnight are not the same object painted twice. So a theme carries a form
79
+ * as well — how square things are, how heavy the rules are, what a card is.
80
+ *
81
+ * The kit still defines the structure; a form only chooses among the shapes
82
+ * the kit already knows how to draw. `shape` picks the drawing, everything
83
+ * else tunes it. All of it is optional: leave the form out and the library
84
+ * renders exactly as it did before forms existed.
85
+ */
86
+ interface DashboardThemeForm {
87
+ /**
88
+ * Which drawing the board uses.
89
+ *
90
+ * - `panel` — cards are raised chips floating in a rounded column, each
91
+ * carrying a coloured bar. The default.
92
+ * - `ledger` — cards are ruled rows in a column with no fill, separated by
93
+ * hairlines and marked in the gutter. Reads as a book rather than a HUD.
94
+ */
95
+ shape?: 'panel' | 'ledger';
96
+ /** Radius of columns, tiles and sheets. */
97
+ radiusPanel?: string;
98
+ /** Radius of cards. */
99
+ radiusCard?: string;
100
+ /** Radius of buttons, chips and fields. */
101
+ radiusControl?: string;
102
+ /** Radius of small square controls. */
103
+ radiusSquare?: string;
104
+ /**
105
+ * Radius of chips, tags and pills. A ledger squares these off; leave it out
106
+ * and they stay fully rounded.
107
+ */
108
+ radiusPill?: string;
109
+ /** Border weight. A ledger wants a hairline; a HUD can afford more. */
110
+ borderWidth?: string;
111
+ /** Body face. */
112
+ fontFamily?: string;
113
+ /** Face for column headers and titles, when it differs from the body. */
114
+ fontFamilyDisplay?: string;
115
+ /** Base text size. */
116
+ fontSize?: string;
117
+ /** Tracking on the small uppercase labels. */
118
+ labelTracking?: string;
119
+ /** Multiplies the spacing scale: below 1 is denser, above 1 is airier. */
120
+ density?: number;
121
+ }
122
+
123
+ interface DashboardThemeProviderProps {
124
+ tokens: DashboardThemeTokens;
125
+ /** Shape roles. Omitted, the library keeps its default "panel" form. */
126
+ form?: DashboardThemeForm;
127
+ children: React.ReactNode;
128
+ className?: string;
129
+ style?: React.CSSProperties;
130
+ }
131
+ /**
132
+ * Injects the project's colour tokens as CSS variables on a container.
133
+ * Everything the library renders inside this provider reads colour through
134
+ * those variables — never from a hard-coded value.
135
+ */
136
+ declare function DashboardThemeProvider({ tokens, form, children, className, style }: DashboardThemeProviderProps): React.JSX.Element;
137
+ /** The active form, or null when the theme did not declare one. */
138
+ declare function useDashboardForm(): DashboardThemeForm | null;
139
+ declare function useDashboardTheme(): DashboardThemeTokens;
140
+
141
+ /**
142
+ * Base themes for projects that do not have a palette of their own yet (see
143
+ * INSTALL.md, step 3). They are nobody's identity — just a decent starting
144
+ * point, meant to be replaced as soon as the project has one.
145
+ *
146
+ * All three follow the same system rules (a single accent, the four semantic
147
+ * colours kept apart from it, three surface levels): what differs between
148
+ * them is the choice of colour, not the structure.
149
+ */
150
+ declare const midnightTheme: DashboardThemeTokens;
151
+ declare const oldMoneyTheme: DashboardThemeTokens;
152
+ declare const cyberpunkTheme: DashboardThemeTokens;
153
+ /**
154
+ * Warm paper and clay. The one light theme the kit ships.
155
+ *
156
+ * Every other preset is dark, which made "light" an untested claim rather than
157
+ * an option: the tokens were always capable of it, but nothing proved a board
158
+ * drawn on a pale ground stayed legible. This is that proof as well as a
159
+ * theme — ink is near-black rather than pure, surfaces separate by warmth
160
+ * instead of by brightness alone, and the accent is a clay that reads against
161
+ * paper without shouting.
162
+ *
163
+ * `accentInk` matters more here than anywhere: on a dark theme an accent is
164
+ * always lighter than what it sits on, so text over it is dark by default. On
165
+ * this one it is the other way round.
166
+ */
167
+ declare const claudeTheme: DashboardThemeTokens;
168
+ /**
169
+ * The default form: raised cards floating in a rounded column. Everything the
170
+ * library drew before forms existed.
171
+ */
172
+ declare const panelForm: DashboardThemeForm;
173
+ /**
174
+ * A ledger. Squared off, ruled rather than boxed, set in a serif with more air
175
+ * between the rows — a book of accounts, not a HUD. Old Money is built on it,
176
+ * which is why that theme is not another one in green.
177
+ */
178
+ declare const ledgerForm: DashboardThemeForm;
179
+ /**
180
+ * Softer than `panel` and roomier: larger radii, a hairline border, and a
181
+ * humanist face. Paper does not need the contrast a dark surface gets for
182
+ * free, so the shape does the separating instead.
183
+ */
184
+ declare const paperForm: DashboardThemeForm;
185
+ declare const THEME_FORMS: {
186
+ readonly panel: DashboardThemeForm;
187
+ readonly ledger: DashboardThemeForm;
188
+ readonly paper: DashboardThemeForm;
189
+ };
190
+ type ThemeFormName = keyof typeof THEME_FORMS;
191
+ declare const THEME_PRESETS: {
192
+ readonly midnight: DashboardThemeTokens;
193
+ readonly oldMoney: DashboardThemeTokens;
194
+ readonly cyberpunk: DashboardThemeTokens;
195
+ readonly claude: DashboardThemeTokens;
196
+ };
197
+ type ThemePresetName = keyof typeof THEME_PRESETS;
198
+
199
+ interface DashboardHeaderProps {
200
+ /** Project name — the component applies uppercase and letter spacing, so pass it as written. */
201
+ wordmark: React.ReactNode;
202
+ /** Compact status line, for example "3 pending". */
203
+ status?: React.ReactNode;
204
+ /** Small controls (buttons, toggles) placed before the text link. */
205
+ controls?: React.ReactNode;
206
+ /** Plain text link at the end of the header. */
207
+ linkLabel?: string;
208
+ linkHref?: string;
209
+ onLinkClick?: () => void;
210
+ }
211
+ declare function DashboardHeader({ wordmark, status, controls, linkLabel, linkHref, onLinkClick }: DashboardHeaderProps): React.JSX.Element;
212
+
213
+ interface AlertBannerProps {
214
+ visible: boolean;
215
+ message: React.ReactNode;
216
+ actionLabel?: string;
217
+ onAction?: () => void;
218
+ }
219
+ /** Full-width banner under the header. Hidden by default (visible=false). */
220
+ declare function AlertBanner({ visible, message, actionLabel, onAction }: AlertBannerProps): React.JSX.Element | null;
221
+
222
+ interface StatTileProps {
223
+ label: string;
224
+ value: React.ReactNode;
225
+ /** Semantic colour for when the value matters, for example something is pending. */
226
+ tone?: 'neutral' | 'danger' | 'warning' | 'success';
227
+ /** When the value is zero or empty, render it calm instead of semantic. */
228
+ isZero?: boolean;
229
+ }
230
+ declare function StatTile({ label, value, tone, isZero }: StatTileProps): React.JSX.Element;
231
+ declare function StatTileGrid({ children }: {
232
+ children: React.ReactNode;
233
+ }): React.JSX.Element;
234
+
235
+ type HealthStatus = 'ok' | 'warning' | 'bad' | 'unknown';
236
+ interface HealthPillProps {
237
+ label: string;
238
+ value: React.ReactNode;
239
+ status: HealthStatus;
240
+ }
241
+ /** Facts about the system itself: credential countdown, environment, last sync. */
242
+ declare function HealthPill({ label, value, status }: HealthPillProps): React.JSX.Element;
243
+ declare function HealthPillRow({ children }: {
244
+ children: React.ReactNode;
245
+ }): React.JSX.Element;
246
+
247
+ interface FilterChipProps {
248
+ label: string;
249
+ count?: number;
250
+ active: boolean;
251
+ onClick: () => void;
252
+ }
253
+ declare function FilterChip({ label, count, active, onClick }: FilterChipProps): React.JSX.Element;
254
+ /** Horizontal scrolling row with no visible scrollbar. */
255
+ declare function FilterChipRow({ children }: {
256
+ children: React.ReactNode;
257
+ }): React.JSX.Element;
258
+
259
+ interface BoardCardData {
260
+ id: string;
261
+ }
262
+ interface BoardColumnData<TCard extends BoardCardData = BoardCardData> {
263
+ id: string;
264
+ title: string;
265
+ /**
266
+ * What the column means, shown on hover and to a screen reader.
267
+ *
268
+ * A column head is two words; whether a card sitting there is waiting on you
269
+ * or on someone else is the thing you actually need, and two words cannot
270
+ * carry it.
271
+ */
272
+ hint?: string;
273
+ cards: TCard[];
274
+ }
275
+ interface BoardMoveResult {
276
+ cardId: string;
277
+ fromColumnId: string;
278
+ toColumnId: string;
279
+ toIndex: number;
280
+ }
281
+
282
+ interface BoardProps<TCard extends BoardCardData> {
283
+ columns: BoardColumnData<TCard>[];
284
+ /** Renders the content of each card — normally a <Card /> from this library. */
285
+ renderCard: (card: TCard, index: number, columnId: string) => React.ReactNode;
286
+ onMove: (result: BoardMoveResult) => void;
287
+ onAddCard?: (columnId: string) => void;
288
+ }
289
+ /** A row of columns with horizontal scroll-snap and drag and drop between them. */
290
+ declare function Board<TCard extends BoardCardData>({ columns, renderCard, onMove, onAddCard }: BoardProps<TCard>): React.JSX.Element;
291
+
292
+ interface BoardTag {
293
+ label: string;
294
+ tone?: 'neutral' | 'danger' | 'warning' | 'success';
295
+ /** The most important tag: inverted to a solid accent fill. */
296
+ emphasize?: boolean;
297
+ }
298
+ interface CardProps {
299
+ cardId: string;
300
+ columnId: string;
301
+ index: number;
302
+ title: React.ReactNode;
303
+ /** Colour of the 3px stripe that encodes priority. */
304
+ priorityColor?: string;
305
+ tags?: BoardTag[];
306
+ progress?: {
307
+ done: number;
308
+ total: number;
309
+ };
310
+ children?: React.ReactNode;
311
+ onClick?: () => void;
312
+ }
313
+ /** Dragging starts only from the grip, so the rest of the card still scrolls the column. */
314
+ declare function Card$1({ cardId, columnId, index, title, priorityColor, tags, progress, children, onClick }: CardProps): React.JSX.Element;
315
+
316
+ /**
317
+ * Applies a BoardMoveResult (what onMove emits) to an array of columns and
318
+ * returns the new array. This is the piece of logic where an off-by-one slips
319
+ * in most easily when every project rewrites it, which is why it lives here
320
+ * rather than in a written convention.
321
+ */
322
+ declare function moveCardInColumns<TCard extends BoardCardData>(columns: BoardColumnData<TCard>[], move: BoardMoveResult): BoardColumnData<TCard>[];
323
+
324
+ interface DetailSheetProps {
325
+ open: boolean;
326
+ onClose: () => void;
327
+ children: React.ReactNode;
328
+ ariaLabel?: string;
329
+ }
330
+ /**
331
+ * Below 760px this is a sheet rising from the bottom; from 760px up the same
332
+ * markup becomes a centred floating dialog. The breakpoint is handled in CSS,
333
+ * so there are not two different components.
334
+ */
335
+ declare function DetailSheet({ open, onClose, children, ariaLabel }: DetailSheetProps): React.JSX.Element | null;
336
+ interface DetailSectionProps {
337
+ title: string;
338
+ /** Clarifying phrase in lowercase, printed after an em dash. */
339
+ subtitle?: string;
340
+ children: React.ReactNode;
341
+ }
342
+ declare function DetailSection({ title, subtitle, children }: DetailSectionProps): React.JSX.Element;
343
+
344
+ /**
345
+ * Whether a note has been dismissed, and how to dismiss it.
346
+ *
347
+ * Starts as "shown but not yet decided" so the first paint matches the server
348
+ * and nothing flashes into place; the stored answer arrives on the first
349
+ * effect.
350
+ */
351
+ declare function useGuideNote(id: string, remember?: boolean): {
352
+ visible: boolean;
353
+ dismiss: () => void;
354
+ };
355
+ /** Clears every dismissal, so the guidance can be asked for again. */
356
+ declare function resetGuide(): void;
357
+ interface GuideNoteProps {
358
+ /** Stable across releases: changing it makes the note reappear for everyone. */
359
+ id: string;
360
+ title: React.ReactNode;
361
+ children: React.ReactNode;
362
+ /** Wording of the dismiss button. Say what it does, not "OK". */
363
+ dismissLabel?: string;
364
+ /** Shown after the note, for the one thing it wants the reader to do. */
365
+ action?: {
366
+ label: string;
367
+ onClick: () => void;
368
+ };
369
+ /** Position in a sequence, so a first session reads as a tour, not a pile. */
370
+ step?: {
371
+ index: number;
372
+ total: number;
373
+ };
374
+ /** Told after the note is dismissed, so a sequence can move on. */
375
+ onDismissed?: () => void;
376
+ /**
377
+ * Whether to record the dismissal in localStorage. False when the caller
378
+ * keeps that answer itself, so the two cannot disagree.
379
+ */
380
+ remember?: boolean;
381
+ }
382
+ declare function GuideNote({ id, title, children, dismissLabel, action, step, onDismissed, remember, }: GuideNoteProps): React.JSX.Element | null;
383
+ interface GuideTourProps {
384
+ /** Notes in order. Only the first undismissed one shows, so it reads as a tour. */
385
+ notes: Array<Omit<GuideNoteProps, 'step' | 'onDismissed'>>;
386
+ /**
387
+ * Which notes are already dismissed, when the caller keeps that somewhere
388
+ * durable. Leave it out and each reader's browser remembers, which is the
389
+ * right home for a fact about one browser — and the wrong one for a panel
390
+ * with a single owner, who would be told the same tour again on a new
391
+ * machine.
392
+ */
393
+ dismissed?: string[];
394
+ /** Told when a note is dismissed. Required for `dismissed` to mean anything. */
395
+ onDismiss?: (id: string) => void;
396
+ }
397
+ /**
398
+ * One note at a time, in order.
399
+ *
400
+ * Five notes at once is a wall nobody reads. Shown one at a time, dismissing
401
+ * each reveals the next, and a reader who dismisses all of them has been
402
+ * through the tour rather than having closed a box.
403
+ */
404
+ declare function GuideTour({ notes, dismissed, onDismiss }: GuideTourProps): React.JSX.Element | null;
405
+
406
+ type NotificationTone = 'info' | 'attention';
407
+ interface PanelNotification {
408
+ /**
409
+ * Stable for the life of the event. Two different events must never share
410
+ * one, or marking the first seen silences the second; the same event must
411
+ * never change it, or it comes back every time the page loads.
412
+ */
413
+ id: string;
414
+ title: React.ReactNode;
415
+ detail?: React.ReactNode;
416
+ /** Shown as given, so the caller decides the format its readers expect. */
417
+ at?: string;
418
+ /** `attention` for something waiting on the reader, `info` for the rest. */
419
+ tone?: NotificationTone;
420
+ /** Where this leads. Called before the notification is marked seen. */
421
+ onOpen?: () => void;
422
+ }
423
+ interface NotificationLabels {
424
+ /** Names the control for a screen reader and on hover. */
425
+ title: string;
426
+ empty: string;
427
+ markAll: string;
428
+ /** Given the count, e.g. (n) => `${n} unread`. */
429
+ unread: (count: number) => string;
430
+ }
431
+ interface NotificationBellProps {
432
+ notifications: PanelNotification[];
433
+ labels?: Partial<NotificationLabels>;
434
+ /**
435
+ * What this reader has already seen, when the caller keeps it somewhere
436
+ * durable. Leave it out and the bell remembers in localStorage, which is the
437
+ * right home for a fact about one browser.
438
+ *
439
+ * A panel with one password has one owner, and there "this reader" and "this
440
+ * project" are the same person — so a panel that saves its board can keep
441
+ * this in it and have it survive a new browser. That is the caller's call to
442
+ * make, not the component's, which is why both work.
443
+ */
444
+ seen?: string[];
445
+ /** Told which ids were just seen. Required for `seen` to mean anything. */
446
+ onSeen?: (ids: string[]) => void;
447
+ }
448
+ declare function NotificationBell({ notifications, labels, seen: given, onSeen }: NotificationBellProps): React.JSX.Element | null;
449
+
450
+ /**
451
+ * A block of text whose whole purpose is to end up somewhere else.
452
+ *
453
+ * Anything a panel tells someone to paste — a settings fragment, two commands,
454
+ * a shell line — is read once and copied. So the copy is the primary action
455
+ * and the text is selectable underneath it, rather than the other way round.
456
+ *
457
+ * The clipboard API is not always there. It needs a secure context and can be
458
+ * refused outright, and a copy button that silently does nothing is worse than
459
+ * no button at all: the reader walks away believing they have the text. So the
460
+ * failure falls back to selecting the block, and says which of the two
461
+ * happened.
462
+ */
463
+ interface CopyBlockProps {
464
+ /** Exactly what lands on the clipboard. Shown verbatim. */
465
+ text: string;
466
+ /** What this block is, above it. */
467
+ label?: React.ReactNode;
468
+ /** Why the reader wants it, and anything they must know before pasting. */
469
+ note?: React.ReactNode;
470
+ /** Wording, so the kit stays language-agnostic. */
471
+ labels?: Partial<CopyBlockLabels>;
472
+ }
473
+ interface CopyBlockLabels {
474
+ copy: string;
475
+ copied: string;
476
+ /** Used when the clipboard refused and the text was selected instead. */
477
+ selected: string;
478
+ }
479
+ declare function CopyBlock({ text, label, note, labels }: CopyBlockProps): React.JSX.Element;
480
+
481
+ /**
482
+ * What the panel needs in order to work, and what to do about what is missing.
483
+ *
484
+ * A panel with no token does not look broken — it looks fine until the moment
485
+ * someone presses Save, and then it fails with something the person reading it
486
+ * did not cause and cannot place. This turns that into a list they can read
487
+ * before it happens, and act on without leaving the panel.
488
+ *
489
+ * The status comes from the server, which is the only place that can see the
490
+ * environment. It reports *whether* a variable is set and never its value, so
491
+ * this component has nothing worth leaking.
492
+ */
493
+ type SetupStatus = 'ok' | 'missing' | 'unknown';
494
+ interface SetupRequirement {
495
+ id: string;
496
+ /** What it is, in the reader's terms rather than the variable's. */
497
+ label: React.ReactNode;
498
+ status: SetupStatus;
499
+ /** The environment variable behind it, when there is one to name. */
500
+ variable?: string;
501
+ /** What it lets the panel do — so a missing one has a visible cost. */
502
+ enables?: React.ReactNode;
503
+ /** Exactly what to do, shown only when it is missing. */
504
+ fix?: React.ReactNode;
505
+ }
506
+ interface SetupGuideProps {
507
+ requirements: SetupRequirement[];
508
+ /** Shown when everything is in place, instead of an empty list. */
509
+ readyMessage?: React.ReactNode;
510
+ /** Wording, so the kit stays language-agnostic. */
511
+ labels?: Partial<{
512
+ ok: string;
513
+ missing: string;
514
+ unknown: string;
515
+ whatToDo: string;
516
+ }>;
517
+ }
518
+ declare function SetupGuide({ requirements, readyMessage, labels }: SetupGuideProps): React.JSX.Element;
519
+
520
+ type Handler = (payload: Record<string, string>, event: MouseEvent) => void;
521
+ /**
522
+ * A single click listener on document that dispatches by data-* attributes,
523
+ * instead of one handler per element. Meant for a project's own actions; the
524
+ * Board does not use it internally.
525
+ *
526
+ * Example: useDelegatedClick('data-action', (payload) => { if (payload.action === 'delete') ... })
527
+ * with buttons such as <button data-action="delete" data-id="42">
528
+ */
529
+ declare function useDelegatedClick(attribute: string, handler: Handler): void;
530
+
531
+ declare function useMediaQuery(query: string): boolean;
532
+
533
+ /**
534
+ * A board is read in one language and written in another.
535
+ *
536
+ * This is the mechanism only — the types and the resolution. The panel's own
537
+ * wording lives with the panel: a library that shipped its copy would be a
538
+ * library deciding what a project's board says.
539
+ */
540
+ type Lang = 'en' | 'es';
541
+ declare const LANGS: Lang[];
542
+ declare const LANG_LABELS: Record<Lang, string>;
543
+ /**
544
+ * A string, or the same string per language.
545
+ *
546
+ * A plain string is a claim: this text is right in either language — true for
547
+ * a product name, a number, a file id, and almost never for prose. An object
548
+ * is the honest shape for a sentence, and one that carries a single language
549
+ * says "written, not translated yet", which is exactly what the next session
550
+ * needs to know.
551
+ */
552
+ type Text = string | Partial<Record<Lang, string>>;
553
+ declare function text(value: Text | undefined, lang: Lang): string;
554
+ /** True when a sentence exists in one language only. */
555
+ declare function untranslated(value: Text | undefined): boolean;
556
+ /**
557
+ * Writing replaces the language being typed and drops the other copy of that
558
+ * field. Deliberate: a translation of a sentence somebody just rewrote is a
559
+ * lie about what the card says, and the gap is the panel telling the next
560
+ * session what to fill in.
561
+ */
562
+ declare function writeText(lang: Lang, value: string): Text;
563
+
564
+ type Weight = 'primary' | 'secondary' | 'off';
565
+ type Severity = 'high' | 'medium' | 'low';
566
+ type SuggestionStatus = 'new' | 'doing' | 'done' | 'dismissed';
567
+ interface Goal {
568
+ id: string;
569
+ label: Text;
570
+ weight: Weight;
571
+ }
572
+ interface Constraint {
573
+ id: string;
574
+ label: Text;
575
+ on: boolean;
576
+ }
577
+ interface Reading {
578
+ date: string;
579
+ value: number;
580
+ note?: Text;
581
+ }
582
+ interface Metric {
583
+ id: string;
584
+ label: Text;
585
+ unit: string;
586
+ /** Which direction counts as better. A reading against it is what "drifting" means. */
587
+ goal: 'up' | 'down';
588
+ readings: Reading[];
589
+ }
590
+ interface Strategy {
591
+ id: string;
592
+ title: Text;
593
+ thesis: Text;
594
+ horizon: Text;
595
+ /** 0–10, so three bars can be compared at a glance rather than read. */
596
+ effort: number;
597
+ risk: number;
598
+ upside: number;
599
+ moves: Text[];
600
+ }
601
+ interface Suggestion {
602
+ id: string;
603
+ area: string;
604
+ severity: Severity;
605
+ status: SuggestionStatus;
606
+ date: string;
607
+ text: Text;
608
+ }
609
+ /**
610
+ * A question the owner answers and Claude reads.
611
+ *
612
+ * This is the whole point of the analysis view. A board says what to do; a
613
+ * choice says how the project wants it done, in the owner's words, once —
614
+ * instead of being asked again on every card. Claude reads these before
615
+ * deciding anything, so answering one here is worth more than answering it
616
+ * in a message that scrolls away.
617
+ *
618
+ * `value` of null is not missing data. It means nobody has decided, which is
619
+ * information: Claude must not guess an answer to a question the owner has
620
+ * deliberately left open.
621
+ */
622
+ interface Choice {
623
+ id: string;
624
+ question: Text;
625
+ /** What actually changes depending on the answer. Never "choose an option". */
626
+ why?: Text;
627
+ options: ChoiceOption[];
628
+ value: string | null;
629
+ decidedAt?: string;
630
+ decidedBy?: string;
631
+ }
632
+ interface ChoiceOption {
633
+ id: string;
634
+ label: Text;
635
+ /** What picking this commits the project to. */
636
+ detail?: Text;
637
+ }
638
+ /** Who the work is for, and against what. */
639
+ interface Market {
640
+ audience: Text;
641
+ positioning: Text;
642
+ competitors: Competitor[];
643
+ pricing?: Text;
644
+ notes?: Text[];
645
+ }
646
+ interface Competitor {
647
+ id: string;
648
+ name: string;
649
+ /** What they do better. Written honestly or it is worthless. */
650
+ strength: Text;
651
+ /** Where they leave room. */
652
+ gap: Text;
653
+ }
654
+ /** The four things the analysis is about, each its own screen. */
655
+ type AnalysisSection = 'board' | 'project' | 'strategy' | 'market';
656
+ declare const ANALYSIS_SECTIONS: AnalysisSection[];
657
+ interface Analysis {
658
+ brief: Text;
659
+ stage: string;
660
+ goals: Goal[];
661
+ constraints: Constraint[];
662
+ metrics: Metric[];
663
+ strategies: Strategy[];
664
+ /** The path the owner picked. Null is a decision waiting, and it is theirs. */
665
+ chosen: string | null;
666
+ suggestions: Suggestion[];
667
+ /** Who this is for and against what. Optional: not every board is a product. */
668
+ market?: Market;
669
+ /**
670
+ * Standing answers, grouped by the screen they belong to. Claude reads them
671
+ * before deciding anything the board does not already settle.
672
+ */
673
+ choices?: Partial<Record<AnalysisSection, Choice[]>>;
674
+ }
675
+ /** Everything the owner has actually decided, newest first. */
676
+ declare function decided(analysis: Analysis): {
677
+ section: AnalysisSection;
678
+ choice: Choice;
679
+ option: ChoiceOption;
680
+ }[];
681
+ /** Questions still open. A count of these is the honest state of the analysis. */
682
+ declare function undecided(analysis: Analysis): {
683
+ section: AnalysisSection;
684
+ choice: Choice;
685
+ }[];
686
+ /**
687
+ * What the board itself says, counted rather than declared.
688
+ *
689
+ * These are not stored: a number typed into the analysis is a number that
690
+ * disagrees with the board the moment a card moves. They are derived on every
691
+ * render from the cards, so they cannot be wrong.
692
+ */
693
+ interface BoardFacts {
694
+ total: number;
695
+ open: number;
696
+ byColumn: {
697
+ id: string;
698
+ title: Text;
699
+ count: number;
700
+ }[];
701
+ waitingOnOwner: number;
702
+ orders: number;
703
+ highRisk: number;
704
+ /** Cards claiming to be finished that were never picked up. */
705
+ claimed: Pick<Card, 'id' | 'title'>[];
706
+ /** Age of the oldest card still open. Null when nothing is open. */
707
+ oldestOpenDays: number | null;
708
+ oldestOpen: Pick<Card, 'id' | 'title'> | null;
709
+ }
710
+ /**
711
+ * Counts the board rather than believing a number somebody typed.
712
+ *
713
+ * `today` is passed in so the same cards always produce the same answer — a
714
+ * function that reads the clock cannot be checked.
715
+ */
716
+ declare function boardFacts(cards: Card[], columns: ColumnDef[], today: string): BoardFacts;
717
+ interface Trend {
718
+ latest: Reading | undefined;
719
+ previous: Reading | undefined;
720
+ delta: number | null;
721
+ /** True when the last move went against the metric's declared direction. */
722
+ against: boolean;
723
+ }
724
+ declare function trendOf(metric: Metric): Trend;
725
+ /**
726
+ * Points for a sparkline in a 0–1 box, so the caller only decides the size.
727
+ * A flat series sits on the middle line rather than collapsing to the floor.
728
+ */
729
+ declare function sparkPoints(readings: Reading[]): {
730
+ x: number;
731
+ y: number;
732
+ }[];
733
+
734
+ type Level = 'high' | 'medium' | 'low';
735
+ type Owner = 'claude' | 'you';
736
+ /**
737
+ * What the owner wants done with a card next, in one tap.
738
+ *
739
+ * Free text would be clearer to write and useless to obey: "solve it if you
740
+ * can" and "solve it yourself" are the same sentence and different
741
+ * instructions. A fixed set gives each one an exact, documented meaning, so a
742
+ * tap is a contract rather than a hint. Absent is a real state: no
743
+ * instruction, use judgement.
744
+ *
745
+ * Three of them — explain, askme, hold — are refusals to act. Honouring them
746
+ * exactly matters more than the work: they are the only way to say "not yet".
747
+ */
748
+ declare const INTENTS: readonly ["decide", "explain", "solve", "do", "cheap", "safe", "fast", "askme", "hold"];
749
+ type Intent = (typeof INTENTS)[number];
750
+ interface IntentCopy {
751
+ label: Record<Lang, string>;
752
+ meaning: Record<Lang, string>;
753
+ }
754
+ declare const INTENT_COPY: Record<Intent, IntentCopy>;
755
+ interface Check {
756
+ id: string;
757
+ text: Text;
758
+ done: boolean;
759
+ }
760
+ interface Note {
761
+ id: string;
762
+ by: Owner;
763
+ date: string;
764
+ text: Text;
765
+ }
766
+ interface Card {
767
+ id: string;
768
+ column: string;
769
+ title: Text;
770
+ body: Text;
771
+ area: string;
772
+ priority: Level;
773
+ risk: Level;
774
+ owner: Owner;
775
+ /** The one flag that means "I am asking you to do this". */
776
+ order: boolean;
777
+ intent: Intent | null;
778
+ checks: Check[];
779
+ notes: Note[];
780
+ createdAt: string;
781
+ updatedAt: string;
782
+ /**
783
+ * Stamped by the board when the card enters the working column, and when it
784
+ * reaches a done column. They are written by the move, never typed, so a
785
+ * card that carries `completedAt` with no `startedAt` is one that was
786
+ * declared finished without ever being picked up — which is exactly the
787
+ * thing worth being able to see.
788
+ */
789
+ startedAt?: string | null;
790
+ completedAt?: string | null;
791
+ }
792
+ interface ColumnDef {
793
+ id: string;
794
+ title: Text;
795
+ /** What the column means, shown on hover. Two words cannot say whether a
796
+ card sitting here is waiting on you or on Claude. */
797
+ hint?: Text;
798
+ /** Work in flight: entering this column stamps the card as started. */
799
+ active?: boolean;
800
+ /** Cards here are finished: they leave the open counts alone. */
801
+ done?: boolean;
802
+ }
803
+ /**
804
+ * One pass over the board: what a session changed, and which cards it
805
+ * touched. Written at the end of a run, so the panel keeps the account
806
+ * rather than the account living in a chat nobody can re-read.
807
+ */
808
+ interface Run {
809
+ id: string;
810
+ date: string;
811
+ by: Owner;
812
+ summary: Text;
813
+ /** Card ids this run touched, so a claim can be checked against the board. */
814
+ cards: string[];
815
+ /** The kit fingerprint at the time, twelve characters is enough to compare. */
816
+ fingerprint?: string;
817
+ }
818
+ interface HealthFact {
819
+ label: Text;
820
+ value: Text;
821
+ status: 'ok' | 'warning' | 'bad' | 'unknown';
822
+ }
823
+ /**
824
+ * A theme Claude derived from the project it is installed in.
825
+ *
826
+ * Stored with where it came from, not only with its colours. "Update the
827
+ * theme" is only a meaningful request if the next answer can be compared with
828
+ * this one — otherwise a regenerated theme is a different theme for reasons
829
+ * nobody can see. `from` is what makes the difference readable.
830
+ *
831
+ * `refreshRequested` is a message, not a trigger: a panel cannot run Claude.
832
+ * Tapping Update writes the date here, the bell shows it, and the next session
833
+ * regenerates. Pretending the button does the work itself would leave the
834
+ * owner waiting for something that was never going to happen.
835
+ */
836
+ interface ImportedTheme {
837
+ /** What to call it in the theme list. */
838
+ label: Text;
839
+ tokens: Record<string, string>;
840
+ form?: Record<string, string | number>;
841
+ from: {
842
+ /** Files the colours were read out of, so the claim can be checked. */
843
+ files: string[];
844
+ /** The colours actually found, before they were mapped to tokens. */
845
+ colours: string[];
846
+ /** Why these tokens and not others. One or two sentences. */
847
+ reasoning: Text;
848
+ };
849
+ importedAt: string;
850
+ importedBy: 'claude';
851
+ /** Set by the panel when the owner asks for it to be derived again. */
852
+ refreshRequested?: string | null;
853
+ }
854
+ /**
855
+ * What the owner has chosen, kept in the board so it survives a new browser.
856
+ *
857
+ * The kit's default is to remember these per browser, in localStorage, because
858
+ * for a component "which notes this reader dismissed" is a fact about a reader
859
+ * and not about a project. A panel with one password is the case where those
860
+ * two are the same person: keeping it here means the theme, the language and
861
+ * what has already been read follow the owner to a new machine instead of
862
+ * greeting them with a tour they finished months ago.
863
+ *
864
+ * The cost is real and worth naming: anyone who shares the password shares
865
+ * this. It holds a theme name and a list of ids — nothing worth stealing, and
866
+ * nothing anyone else would want.
867
+ */
868
+ interface Preferences {
869
+ theme?: string;
870
+ lang?: Lang;
871
+ /** Ids of notifications already read. Bounded when saved. */
872
+ seen?: string[];
873
+ /** Ids of guide notes already dismissed. */
874
+ guideDismissed?: string[];
875
+ }
876
+ interface PanelState {
877
+ v: 2;
878
+ project: string;
879
+ environment: Text;
880
+ updatedAt: string;
881
+ notice?: Text;
882
+ columns: ColumnDef[];
883
+ areas: {
884
+ id: string;
885
+ label: Text;
886
+ }[];
887
+ health: HealthFact[];
888
+ cards: Card[];
889
+ /** Newest first. Capped when saving, so the file cannot grow without end. */
890
+ runs: Run[];
891
+ /** What any of the work is for: goals, constraints, metrics, strategies. */
892
+ analysis?: Analysis;
893
+ /** The owner's choices, so they survive a new browser. */
894
+ preferences?: Preferences;
895
+ /** A theme Claude derived from this project, with where it came from. */
896
+ importedTheme?: ImportedTheme;
897
+ }
898
+ declare const LEVELS: Level[];
899
+ declare const OWNERS: Owner[];
900
+ declare function newId(): string;
901
+ declare function today(): string;
902
+ /** A card is created empty and named second — the same as tapping "+" on paper. */
903
+ declare function emptyCard(column: string, area: string): Card;
904
+ /**
905
+ * Applies the column's own meaning to a card that just moved into it: a card
906
+ * entering the working column is started, one reaching a done column is
907
+ * finished, and one dragged back out of done is not finished any more.
908
+ */
909
+ declare function stampForColumn(card: Card, columns: ColumnDef[]): Card;
910
+ /** A card claiming to be finished that was never picked up. */
911
+ declare function claimedWithoutStarting(card: Card): boolean;
912
+ interface Counts {
913
+ open: number;
914
+ orders: number;
915
+ yours: number;
916
+ decisions: number;
917
+ highRisk: number;
918
+ done: number;
919
+ }
920
+ declare function countCards(state: PanelState): Counts;
921
+ /** How many checklist steps are ticked, for the counter on a card. */
922
+ declare function checkProgress(card: Card): {
923
+ done: number;
924
+ total: number;
925
+ } | undefined;
926
+
927
+ /**
928
+ * Reads a board and says what is wrong with it, in words.
929
+ *
930
+ * The board is a JSON file in somebody's repository. That is the whole point —
931
+ * it has a diff, a history and an author — and it is also the reason this
932
+ * exists: a file anyone can open is a file anyone can break, and until now
933
+ * nothing looked. `v: 2` was declared in the type and checked nowhere. A
934
+ * renamed column left its cards in a place that does not exist and they simply
935
+ * stopped being drawn; a missing `areas` rendered as a fault with no message.
936
+ *
937
+ * So the shape is checked at both ends. On the way in, so a panel with a
938
+ * broken board says which line is broken instead of showing an empty screen.
939
+ * On the way out, so a write that would break it is refused while the good
940
+ * copy is still the one in the repository — that is the only moment refusing
941
+ * is cheap.
942
+ *
943
+ * Two things this deliberately does not do. It does not repair: a board
944
+ * quietly corrected is a board whose owner never learns what they typed. And
945
+ * it does not judge content — an empty title, a card nobody will ever do, a
946
+ * question with no answer are all legitimate states of a real board.
947
+ */
948
+
949
+ /**
950
+ * The shape this version of the kit reads.
951
+ *
952
+ * A board carrying an older number is not broken, it is old, and that is a
953
+ * different answer: `readBoard` reports it as something to migrate rather than
954
+ * something to fix.
955
+ */
956
+ declare const BOARD_VERSION = 2;
957
+ interface BoardProblem {
958
+ /** Where it is, in the shape a person can find in the file: `cards[3].column`. */
959
+ at: string;
960
+ /** What is wrong, as a sentence. */
961
+ says: string;
962
+ }
963
+ type BoardCheck = {
964
+ ok: true;
965
+ board: PanelState;
966
+ problems: [];
967
+ }
968
+ /** The shape is wrong. `problems` says where, and is never empty. */
969
+ | {
970
+ ok: false;
971
+ reason: 'shape';
972
+ problems: BoardProblem[];
973
+ }
974
+ /** The shape is right but written for a different version of the kit. */
975
+ | {
976
+ ok: false;
977
+ reason: 'version';
978
+ found: unknown;
979
+ expected: number;
980
+ problems: BoardProblem[];
981
+ };
982
+ /**
983
+ * Checks a parsed board.
984
+ *
985
+ * Every problem is collected rather than thrown at the first one: somebody
986
+ * fixing a file by hand wants the list, not one item of it at a time.
987
+ */
988
+ declare function validateBoard(value: unknown): BoardCheck;
989
+ /**
990
+ * Parses text and checks it, so a caller holding a file has one call.
991
+ *
992
+ * Invalid JSON is a shape problem like any other. It arrives with the parser's
993
+ * own message, which names the line — more useful than anything worth
994
+ * rewriting.
995
+ */
996
+ declare function readBoard(text: string): BoardCheck;
997
+ /** The problems as lines, for a log, an HTTP body or a screen. */
998
+ declare function explainBoard(check: BoardCheck): string;
999
+ /** Narrowing helper, so a caller can keep a `Card` without repeating the cast. */
1000
+ declare function isCard(value: unknown): value is Card;
1001
+
1002
+ /**
1003
+ * The identities a panel offers in its settings sheet.
1004
+ *
1005
+ * The site kept this as four parallel records — tokens, forms, labels, notes —
1006
+ * keyed by the same name, which is four places to remember when a theme is
1007
+ * added and four chances to add it to three of them. One record of objects
1008
+ * cannot fall out of step with itself.
1009
+ *
1010
+ * A panel that passes none gets the kit's presets, so a project installs the
1011
+ * panel and has something to switch between on the first session rather than a
1012
+ * settings sheet with one entry.
1013
+ */
1014
+
1015
+ interface PanelTheme {
1016
+ /** What the button says. Not translated: a name is a name. */
1017
+ label: string;
1018
+ tokens: DashboardThemeTokens;
1019
+ /**
1020
+ * The shape it is drawn in. Colour alone would make one identity another in
1021
+ * a different hue; a form makes it a different object.
1022
+ */
1023
+ form?: DashboardThemeForm;
1024
+ /** One line under the row, saying what this one is. */
1025
+ note?: string;
1026
+ }
1027
+ type PanelThemes = Record<string, PanelTheme>;
1028
+ /** Every preset the kit ships, as a panel would offer them. */
1029
+ declare const PRESET_PANEL_THEMES: PanelThemes;
1030
+
1031
+ /**
1032
+ * What the panel shows for the dfklabs catalogue, and the three ways to
1033
+ * install something from it.
1034
+ *
1035
+ * The snippets are built here rather than written into the markup so that the
1036
+ * one a reader copies is generated from the same tool record the row above it
1037
+ * was drawn from. A snippet typed out by hand next to a list is a snippet that
1038
+ * names the wrong tool the first time a name changes.
1039
+ */
1040
+ declare const MARKETPLACE_NAME = "dfklabs";
1041
+ declare const MARKETPLACE_URL = "https://studio.dfklabs.com/marketplace.json";
1042
+ /**
1043
+ * Which marketplace a snippet names.
1044
+ *
1045
+ * Taken from the report rather than from the constants above, so the panel
1046
+ * cannot tell someone to register one marketplace while showing them the
1047
+ * contents of another — which is exactly what a hard-coded name does the first
1048
+ * time the same screen is pointed somewhere else.
1049
+ */
1050
+ interface Marketplace {
1051
+ name: string;
1052
+ url: string;
1053
+ }
1054
+ type InstallState = 'active' | 'disabled' | 'absent' | 'misconfigured' | 'unknown';
1055
+ type SettingsStatus = 'ok' | 'absent' | 'unparseable' | 'unreadable';
1056
+ interface MarketplaceTool {
1057
+ id: string;
1058
+ name: string;
1059
+ summary: string | null;
1060
+ repo: string | null;
1061
+ homepage: string | null;
1062
+ tags: string[];
1063
+ claudePlugin: boolean;
1064
+ standaloneCommand: string | null;
1065
+ version: string | null;
1066
+ versionError: string | null;
1067
+ /** Null when the catalogue does not offer this as a plugin. */
1068
+ state: InstallState | null;
1069
+ }
1070
+ interface MarketplaceReport {
1071
+ marketplace: {
1072
+ name: string;
1073
+ url: string;
1074
+ };
1075
+ source: string;
1076
+ fetchedAt: string;
1077
+ ageSeconds: number;
1078
+ /** True when this is the last good copy, served because a refresh failed. */
1079
+ stale: boolean;
1080
+ error: string | null;
1081
+ /** True only when there is nothing at all to show. */
1082
+ unavailable: boolean;
1083
+ project: {
1084
+ repo: string;
1085
+ file: string;
1086
+ settingsStatus: SettingsStatus;
1087
+ marketplaceRegistered: boolean;
1088
+ marketplaceUrl: string | null;
1089
+ detail: string | null;
1090
+ };
1091
+ tools: MarketplaceTool[];
1092
+ }
1093
+ /**
1094
+ * The two keys a repository needs, as JSON to merge into its settings.
1095
+ *
1096
+ * Merged, not pasted over: a project's settings file holds other things, and a
1097
+ * snippet that replaces it is a snippet that quietly removes them. The panel
1098
+ * says so beside the block, because the block itself cannot.
1099
+ *
1100
+ * The shape is the one a real Claude Code install writes — an object keyed by
1101
+ * marketplace name, and a plugin key carrying the marketplace suffix — rather
1102
+ * than the one the documentation describes. Written the documented way it
1103
+ * parses, saves, and installs nothing.
1104
+ */
1105
+ declare function repositorySnippet(toolId: string, marketplace?: Marketplace): string;
1106
+ /** The two commands, for Claude Code in a terminal or the desktop app. */
1107
+ declare function commandSnippet(toolId: string, marketplace?: Marketplace): string;
1108
+
1109
+ declare function readStoredLang(fallback?: Lang): Lang;
1110
+ declare function storeLang(lang: Lang): void;
1111
+ interface UiStrings {
1112
+ settings: string;
1113
+ dismiss: string;
1114
+ close: string;
1115
+ all: string;
1116
+ board: string;
1117
+ analysis: string;
1118
+ cardDetail: string;
1119
+ panelSettings: string;
1120
+ title: string;
1121
+ titlePlaceholder: string;
1122
+ untitled: string;
1123
+ detail: string;
1124
+ detailPlaceholder: string;
1125
+ areaLabel: string;
1126
+ priority: string;
1127
+ risk: string;
1128
+ riskHint: string;
1129
+ levelHigh: string;
1130
+ levelMedium: string;
1131
+ levelLow: string;
1132
+ dependsOn: string;
1133
+ ownerClaude: string;
1134
+ ownerYou: string;
1135
+ orderTitle: string;
1136
+ orderOn: string;
1137
+ orderOff: string;
1138
+ intentTitle: string;
1139
+ intentHint: string;
1140
+ intentNone: string;
1141
+ checklist: string;
1142
+ addStep: string;
1143
+ addStepPlaceholder: string;
1144
+ notes: string;
1145
+ notePlaceholder: string;
1146
+ leaveNote: string;
1147
+ columnTitle: string;
1148
+ started: string;
1149
+ completed: string;
1150
+ neverStarted: string;
1151
+ deleteCard: string;
1152
+ addCard: string;
1153
+ untranslatedBadge: string;
1154
+ filterForClaude: string;
1155
+ filterYours: string;
1156
+ filterHighRisk: string;
1157
+ statOpen: string;
1158
+ statOrders: string;
1159
+ statYours: string;
1160
+ statDecisions: string;
1161
+ statHighRisk: string;
1162
+ statDone: string;
1163
+ theme: string;
1164
+ themeSubtitle: string;
1165
+ themeNote: string;
1166
+ language: string;
1167
+ languageSubtitle: string;
1168
+ fingerprint: string;
1169
+ fingerprintSubtitle: string;
1170
+ trackedFiles: (count: number, version: string) => string;
1171
+ notifications: string;
1172
+ nothingNew: string;
1173
+ markAllSeen: string;
1174
+ unreadCount: (n: number) => string;
1175
+ notifRun: (by: string) => string;
1176
+ notifWaiting: string;
1177
+ notifSuggestion: (area: string) => string;
1178
+ help: string;
1179
+ helpTitle: string;
1180
+ setupReady: string;
1181
+ setupOk: string;
1182
+ setupMissing: string;
1183
+ setupUnknown: string;
1184
+ setupWhatToDo: string;
1185
+ setupChecking: string;
1186
+ setupWriteTarget: (repo: string, branch: string, file: string) => string;
1187
+ needPassword: string;
1188
+ needPasswordEnables: string;
1189
+ needToken: string;
1190
+ needTokenEnables: string;
1191
+ needRepo: string;
1192
+ needRepoEnables: string;
1193
+ fixTokenCreate: string;
1194
+ fixTokenScope: string;
1195
+ fixTokenPaste: string;
1196
+ fixTokenRedeploy: string;
1197
+ fixRepo: string;
1198
+ demoTourWelcomeTitle: string;
1199
+ demoTourWelcomeBody: string;
1200
+ demoTourBoardTitle: string;
1201
+ demoTourBoardBody: string;
1202
+ demoTourThemeTitle: string;
1203
+ demoTourThemeBody: string;
1204
+ demoTourKeepTitle: string;
1205
+ demoTourKeepBody: string;
1206
+ tourWelcomeTitle: string;
1207
+ tourWelcomeBody: string;
1208
+ tourBoardTitle: string;
1209
+ tourBoardBody: string;
1210
+ tourOrdersTitle: string;
1211
+ tourOrdersBody: string;
1212
+ tourSaveTitle: string;
1213
+ tourSaveBody: string;
1214
+ tourThemeTitle: string;
1215
+ tourThemeBody: string;
1216
+ gotIt: string;
1217
+ showGuideAgain: string;
1218
+ guideSection: string;
1219
+ session: string;
1220
+ sessionSubtitle: string;
1221
+ signOut: string;
1222
+ signingOut: string;
1223
+ signOutFailed: string;
1224
+ links: string;
1225
+ home: string;
1226
+ demo: string;
1227
+ repository: string;
1228
+ terms: string;
1229
+ save: string;
1230
+ saving: string;
1231
+ savedAt: (time: string) => string;
1232
+ savedWithCommit: string;
1233
+ saveFailed: string;
1234
+ saveConflict: string;
1235
+ unsaved: string;
1236
+ saveSection: string;
1237
+ saveSubtitle: string;
1238
+ saveHelp: string;
1239
+ upToDate: string;
1240
+ demoNotice: string;
1241
+ historyEyebrow: string;
1242
+ historyTitle: string;
1243
+ historyIntro: string;
1244
+ historyEmpty: string;
1245
+ historyTouched: string;
1246
+ analysisEyebrow: string;
1247
+ briefTitle: string;
1248
+ stage: string;
1249
+ goalsTitle: string;
1250
+ constraintsTitle: string;
1251
+ constraintOn: string;
1252
+ constraintOff: string;
1253
+ weights: Record<'primary' | 'secondary' | 'off', string>;
1254
+ metricsTitle: string;
1255
+ metricsIntro: string;
1256
+ metricWith: string;
1257
+ metricAgainst: string;
1258
+ strategiesTitle: string;
1259
+ strategyOpenHint: string;
1260
+ strategyChosenHint: string;
1261
+ strategyChoose: string;
1262
+ strategyChosen: string;
1263
+ effort: string;
1264
+ riskShort: string;
1265
+ upside: string;
1266
+ suggestionsTitle: string;
1267
+ suggestionsIntro: string;
1268
+ severities: Record<'high' | 'medium' | 'low', string>;
1269
+ suggestionStatuses: Record<'new' | 'doing' | 'done' | 'dismissed', string>;
1270
+ analysisSections: Record<AnalysisSection, string>;
1271
+ analysisSectionHints: Record<AnalysisSection, string>;
1272
+ analysisAnswered: (answered: number, total: number) => string;
1273
+ analysisAllDecided: string;
1274
+ choicesTitle: string;
1275
+ choicesIntro: string;
1276
+ choiceOpen: string;
1277
+ choiceDecidedAt: (at: string) => string;
1278
+ choiceReopen: string;
1279
+ choicesNone: string;
1280
+ claudeReadsTitle: string;
1281
+ claudeReadsIntro: string;
1282
+ claudeOpenTitle: string;
1283
+ claudeOpenIntro: string;
1284
+ boardFactsTitle: string;
1285
+ boardFactsIntro: string;
1286
+ factOpen: string;
1287
+ factWaiting: string;
1288
+ factOrders: string;
1289
+ factHighRisk: string;
1290
+ factOldest: string;
1291
+ factDays: (n: number) => string;
1292
+ factColumnsTitle: string;
1293
+ factClaimedTitle: string;
1294
+ factClaimedIntro: string;
1295
+ factClaimedNone: string;
1296
+ marketIntro: string;
1297
+ marketEmpty: string;
1298
+ audienceTitle: string;
1299
+ positioningTitle: string;
1300
+ competitorsTitle: string;
1301
+ competitorStrength: string;
1302
+ competitorGap: string;
1303
+ pricingTitle: string;
1304
+ marketNotesTitle: string;
1305
+ importedTheme: string;
1306
+ importedFrom: (at: string, files: string) => string;
1307
+ importedRefresh: string;
1308
+ importedRefreshAsked: (at: string) => string;
1309
+ importedRefreshHow: string;
1310
+ notifThemeRefresh: string;
1311
+ marketplace: string;
1312
+ marketplaceEyebrow: string;
1313
+ marketplaceTitle: string;
1314
+ marketplaceIntro: string;
1315
+ marketplaceSource: (url: string) => string;
1316
+ refresh: string;
1317
+ refreshing: string;
1318
+ readAt: (age: string) => string;
1319
+ catalogueStale: (age: string) => string;
1320
+ catalogueUnavailable: string;
1321
+ catalogueFailed: string;
1322
+ catalogueLoading: string;
1323
+ catalogueEmpty: string;
1324
+ stateFrom: (repo: string, file: string) => string;
1325
+ settingsAbsent: string;
1326
+ settingsUnreadable: string;
1327
+ settingsUnparseable: string;
1328
+ filterPlugins: string;
1329
+ filterActive: string;
1330
+ filterStandalone: string;
1331
+ installStates: Record<InstallState, string>;
1332
+ stateMisconfiguredWhy: string;
1333
+ toolCommandOnly: string;
1334
+ versionUnknown: string;
1335
+ runIt: string;
1336
+ install: string;
1337
+ howToRun: string;
1338
+ learnMore: string;
1339
+ copy: string;
1340
+ copied: string;
1341
+ copySelected: string;
1342
+ installTitle: string;
1343
+ installRepoTitle: string;
1344
+ installRepoHint: string;
1345
+ installRepoMerge: string;
1346
+ installRepoMergeRegistered: string;
1347
+ installCliTitle: string;
1348
+ installCliHint: string;
1349
+ installStandaloneTitle: string;
1350
+ installStandaloneHint: string;
1351
+ installNoStandalone: string;
1352
+ installNoPlugin: string;
1353
+ brandEyebrow: string;
1354
+ logosTitle: string;
1355
+ logosIntro: string;
1356
+ functionsEyebrow: string;
1357
+ functionsTitle: string;
1358
+ functionsIntro: string;
1359
+ }
1360
+ declare const UI: Record<Lang, UiStrings>;
1361
+
1362
+ /**
1363
+ * The Functions section of the panel: what the kit can do for a project
1364
+ * today, and how to wire Claude to it so that keeping the panel current
1365
+ * costs as few tokens as possible. Bilingual, because it is read from the
1366
+ * private panel.
1367
+ */
1368
+ interface Capability {
1369
+ id: string;
1370
+ title: Text;
1371
+ summary: Text;
1372
+ points: Text[];
1373
+ code?: {
1374
+ caption: Text;
1375
+ body: string;
1376
+ };
1377
+ }
1378
+ declare const CAPABILITIES: Capability[];
1379
+
1380
+ interface PanelAppProps {
1381
+ state: PanelState;
1382
+ /**
1383
+ * The identities the settings sheet offers. Defaults to the kit's presets,
1384
+ * so a panel has something to switch between on its first session.
1385
+ */
1386
+ themes?: PanelThemes;
1387
+ /** Which one to draw before the board has said. Defaults to the first. */
1388
+ defaultTheme?: string;
1389
+ /**
1390
+ * What sits where a name goes in the header.
1391
+ *
1392
+ * A string by default — this project's own name, which is what a board
1393
+ * belongs to. A caller with a wordmark passes it here; the kit has no
1394
+ * business drawing anybody's mark, including its own.
1395
+ */
1396
+ brand?: React.ReactNode;
1397
+ /** Rendered inside the theme provider, behind everything: a caller's screen effects. */
1398
+ decoration?: React.ReactNode;
1399
+ /** Put on the theme root, so a caller's stylesheet has something to hang on. */
1400
+ className?: string;
1401
+ /** Rendered under the panel. A caller's footer, or nothing. */
1402
+ footer?: React.ReactNode;
1403
+ /** The Links section of the settings sheet. Omitted when there are none. */
1404
+ links?: {
1405
+ label: string;
1406
+ href: string;
1407
+ }[];
1408
+ /**
1409
+ * The Functions section: what the kit does and how an agent should use it.
1410
+ * Pass a list to show it, or leave it out and the section is not drawn.
1411
+ * `CAPABILITIES` is the kit's own, which is what most panels want.
1412
+ */
1413
+ capabilities?: Capability[];
1414
+ /** An extra section of the caller's own, drawn above Functions. */
1415
+ extras?: React.ReactNode;
1416
+ /** Language switch. The public pages stay in English; the panel does not. */
1417
+ showLanguage?: boolean;
1418
+ /** The revision log: one entry per session, newest first. */
1419
+ showHistory?: boolean;
1420
+ /** Endpoint that commits the board back to the repository, when there is one. */
1421
+ saveEndpoint?: string;
1422
+ /**
1423
+ * Endpoint that assembles the dfklabs catalogue server-side. Present only
1424
+ * where there is a server and a session — the demo is handed a fixed
1425
+ * catalogue instead, so the screen exists in both without the public one
1426
+ * reaching for an endpoint it cannot call.
1427
+ */
1428
+ marketplaceEndpoint?: string;
1429
+ /** A ready-made report, for a panel with no server to ask. */
1430
+ marketplaceReport?: MarketplaceReport;
1431
+ fingerprint?: {
1432
+ version: string;
1433
+ hash: string;
1434
+ fileCount: number;
1435
+ };
1436
+ }
1437
+ declare function PanelApp({ state: initialState, themes, defaultTheme, brand, decoration, className, footer, links, capabilities, extras, showLanguage, showHistory, saveEndpoint, marketplaceEndpoint, marketplaceReport, fingerprint, }: PanelAppProps): React.JSX.Element;
1438
+
1439
+ /**
1440
+ * Applies a board move to the flat card list.
1441
+ *
1442
+ * The board can be filtered, so `toIndex` counts visible cards only. Dropping
1443
+ * on the third visible card must not reorder the hidden ones: the move is
1444
+ * anchored to the visible card it lands before, and the rest of the column
1445
+ * keeps its relative order.
1446
+ */
1447
+ declare function applyMove(cards: Card[], move: BoardMoveResult, visible: Card[]): Card[];
1448
+
1449
+ interface AnalysisViewProps {
1450
+ analysis: Analysis;
1451
+ /** The cards themselves: the Board screen counts them rather than trusting a stored number. */
1452
+ cards: Card[];
1453
+ columns: ColumnDef[];
1454
+ today: string;
1455
+ lang: Lang;
1456
+ ui: UiStrings;
1457
+ onChooseStrategy: (id: string) => void;
1458
+ onSuggestionStatus: (id: string, status: SuggestionStatus) => void;
1459
+ onDecide: (section: AnalysisSection, choiceId: string, optionId: string | null) => void;
1460
+ /** Opening the card a fact is about, so a number is never a dead end. */
1461
+ onOpenCard?: (id: string) => void;
1462
+ }
1463
+ declare function AnalysisView({ analysis, cards, columns, today, lang, ui, onChooseStrategy, onSuggestionStatus, onDecide, onOpenCard, }: AnalysisViewProps): React.JSX.Element;
1464
+
1465
+ interface MarketplaceViewProps {
1466
+ /** Null while the catalogue is still being asked for. */
1467
+ report: MarketplaceReport | null;
1468
+ loading: boolean;
1469
+ /** Set when the panel could not reach its own server. */
1470
+ failure: string | null;
1471
+ lang: Lang;
1472
+ ui: UiStrings;
1473
+ /** Absent where there is no server to ask, so no dead button is drawn. */
1474
+ onRefresh?: () => void;
1475
+ }
1476
+ declare function MarketplaceView({ report, loading, failure, lang, ui, onRefresh }: MarketplaceViewProps): React.JSX.Element;
1477
+
1478
+ interface CardSheetProps {
1479
+ card: Card | null;
1480
+ state: PanelState;
1481
+ lang: Lang;
1482
+ ui: UiStrings;
1483
+ onChange: (card: Card) => void;
1484
+ onDelete: (id: string) => void;
1485
+ onClose: () => void;
1486
+ }
1487
+ declare function CardSheet({ card, state, lang, ui, onChange, onDelete, onClose }: CardSheetProps): React.JSX.Element | null;
1488
+
1489
+ export { ANALYSIS_SECTIONS, AlertBanner, type AlertBannerProps, type Analysis, type AnalysisSection, AnalysisView, BOARD_VERSION, Board, type BoardCardData, type BoardCheck, type BoardColumnData, type BoardFacts, type BoardMoveResult, type BoardProblem, type BoardProps, type BoardTag, CAPABILITIES, type Capability, Card$1 as Card, type CardProps, CardSheet, type Check, type Choice, type ChoiceOption, type ColumnDef, type Competitor, type Constraint, CopyBlock, type CopyBlockLabels, type CopyBlockProps, type Counts, DashboardHeader, type DashboardHeaderProps, type DashboardThemeForm, DashboardThemeProvider, type DashboardThemeTokens, DetailSection, type DetailSectionProps, DetailSheet, type DetailSheetProps, FilterChip, type FilterChipProps, FilterChipRow, type Goal, GuideNote, type GuideNoteProps, GuideTour, type GuideTourProps, type HealthFact, HealthPill, type HealthPillProps, HealthPillRow, type HealthStatus, INTENTS, INTENT_COPY, type ImportedTheme, type InstallState, type Intent, type IntentCopy, LANGS, LANG_LABELS, LEVELS, type Lang, type Level, MARKETPLACE_NAME, MARKETPLACE_URL, type Market, type MarketplaceReport, type MarketplaceTool, MarketplaceView, type Metric, type Note, NotificationBell, type NotificationBellProps, type NotificationLabels, type NotificationTone, OWNERS, type Owner, PANELTIR_FILE_COUNT, PANELTIR_FINGERPRINT, PANELTIR_VERSION, PRESET_PANEL_THEMES, PanelApp, type PanelAppProps, type Card as PanelCard, type PanelNotification, type PanelState, type PanelTheme, type PanelThemes, type Preferences, type Reading, type Run, SetupGuide, type SetupGuideProps, type SetupRequirement, type SetupStatus, type Severity, StatTile, StatTileGrid, type StatTileProps, type Strategy, type Suggestion, type SuggestionStatus, THEME_FORMS, THEME_PRESETS, type Text, type ThemeFormName, type ThemePresetName, type Trend, UI, type UiStrings, type Weight, applyMove, boardFacts, checkProgress, claimedWithoutStarting, claudeTheme, commandSnippet, countCards, cyberpunkTheme, decided, emptyCard, explainBoard, isCard, ledgerForm, midnightTheme, moveCardInColumns, newId, oldMoneyTheme, panelForm, paneltirBuild, paperForm, readBoard, readStoredLang, repositorySnippet, resetGuide, sparkPoints, stampForColumn, storeLang, text, today, trendOf, undecided, untranslated, useDashboardForm, useDashboardTheme, useDelegatedClick, useGuideNote, useMediaQuery, validateBoard, writeText };