paneltir 0.12.0 → 0.13.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/index.d.ts CHANGED
@@ -378,6 +378,12 @@ interface BoardMoveResult {
378
378
  cardId: string;
379
379
  fromColumnId: string;
380
380
  toColumnId: string;
381
+ /**
382
+ * Where to insert the card in the destination column *once it has been
383
+ * taken out of wherever it was*. So a reducer removes the card and then
384
+ * inserts at exactly this index, with no arithmetic of its own — the
385
+ * adjustment for a card moving down its own column is already made.
386
+ */
381
387
  toIndex: number;
382
388
  }
383
389
 
@@ -423,438 +429,118 @@ declare function Card$1({ cardId, columnId, index, title, priorityColor, tags, p
423
429
  */
424
430
  declare function moveCardInColumns<TCard extends BoardCardData>(columns: BoardColumnData<TCard>[], move: BoardMoveResult): BoardColumnData<TCard>[];
425
431
 
426
- interface DetailSheetProps {
427
- open: boolean;
428
- onClose: () => void;
429
- children: React.ReactNode;
430
- ariaLabel?: string;
432
+ interface DropTarget {
433
+ columnId: string;
434
+ index: number;
431
435
  }
432
436
  /**
433
- * Below 760px this is a sheet rising from the bottom; from 760px up the same
434
- * markup becomes a centred floating dialog. The breakpoint is handled in CSS,
435
- * so there are not two different components.
437
+ * What `onMove` is told, given where the drag began and where it ended.
438
+ *
439
+ * Both are rendered positions: the source card stays in its slot at reduced
440
+ * opacity while it is dragged, so `data-pt-card-index` counts it, and the
441
+ * placeholder is drawn *before* the card at `target.index`. `toIndex` is a
442
+ * different thing — the position to insert at once the card is taken out —
443
+ * and the two differ by exactly one whenever a card moves down its own
444
+ * column. Neither reducer allowed for that, so a card dropped before the
445
+ * fourth card landed after it: one slot lower than the placeholder had
446
+ * promised, every time, in both copies of the arithmetic. Adjusted here, once,
447
+ * where the numbers are still known to be rendered ones.
448
+ *
449
+ * `null` means the drop is where the card already was — its own slot, or the
450
+ * slot immediately after it, which is the same place once it is removed.
436
451
  */
437
- declare function DetailSheet({ open, onClose, children, ariaLabel }: DetailSheetProps): React.JSX.Element | null;
438
- interface DetailSectionProps {
439
- title: string;
440
- /** Clarifying phrase in lowercase, printed after an em dash. */
441
- subtitle?: string;
442
- children: React.ReactNode;
443
- }
444
- declare function DetailSection({ title, subtitle, children }: DetailSectionProps): React.JSX.Element;
452
+ declare function dropIndex(source: DropTarget, target: DropTarget): number | null;
445
453
 
446
454
  /**
447
- * Whether a note has been dismissed, and how to dismiss it.
455
+ * A board is read in one language and written in another.
448
456
  *
449
- * Starts as "shown but not yet decided" so the first paint matches the server
450
- * and nothing flashes into place; the stored answer arrives on the first
451
- * effect.
457
+ * This is the mechanism only — the types and the resolution. The panel's own
458
+ * wording lives with the panel: a library that shipped its copy would be a
459
+ * library deciding what a project's board says.
452
460
  */
453
- declare function useGuideNote(id: string, remember?: boolean): {
454
- visible: boolean;
455
- dismiss: () => void;
456
- };
457
- /** Clears every dismissal, so the guidance can be asked for again. */
458
- declare function resetGuide(): void;
459
- interface GuideNoteProps {
460
- /** Stable across releases: changing it makes the note reappear for everyone. */
461
- id: string;
462
- title: React.ReactNode;
463
- children: React.ReactNode;
464
- /** Wording of the dismiss button. Say what it does, not "OK". */
465
- dismissLabel?: string;
466
- /** Shown after the note, for the one thing it wants the reader to do. */
467
- action?: {
468
- label: string;
469
- onClick: () => void;
470
- };
471
- /** Position in a sequence, so a first session reads as a tour, not a pile. */
472
- step?: {
473
- index: number;
474
- total: number;
475
- };
476
- /** Told after the note is dismissed, so a sequence can move on. */
477
- onDismissed?: () => void;
478
- /**
479
- * Whether to record the dismissal in localStorage. False when the caller
480
- * keeps that answer itself, so the two cannot disagree.
481
- */
482
- remember?: boolean;
483
- }
484
- declare function GuideNote({ id, title, children, dismissLabel, action, step, onDismissed, remember, }: GuideNoteProps): React.JSX.Element | null;
485
- interface GuideTourProps {
486
- /** Notes in order. Only the first undismissed one shows, so it reads as a tour. */
487
- notes: Array<Omit<GuideNoteProps, 'step' | 'onDismissed'>>;
488
- /**
489
- * Which notes are already dismissed, when the caller keeps that somewhere
490
- * durable. Leave it out and each reader's browser remembers, which is the
491
- * right home for a fact about one browser — and the wrong one for a panel
492
- * with a single owner, who would be told the same tour again on a new
493
- * machine. Passing it does not switch the browser off: a note dismissed
494
- * here is written to both, because a caller whose store needs saving would
495
- * otherwise lose the dismissal on reload.
496
- */
497
- dismissed?: string[];
498
- /** Told when a note is dismissed. Required for `dismissed` to mean anything. */
499
- onDismiss?: (id: string) => void;
500
- }
461
+ type Lang = 'en' | 'es';
462
+ declare const LANGS: Lang[];
463
+ declare const LANG_LABELS: Record<Lang, string>;
501
464
  /**
502
- * One note at a time, in order.
465
+ * A string, or the same string per language.
503
466
  *
504
- * Five notes at once is a wall nobody reads. Shown one at a time, dismissing
505
- * each reveals the next, and a reader who dismisses all of them has been
506
- * through the tour rather than having closed a box.
467
+ * A plain string is a claim: this text is right in either language — true for
468
+ * a product name, a number, a file id, and almost never for prose. An object
469
+ * is the honest shape for a sentence, and one that carries a single language
470
+ * says "written, not translated yet", which is exactly what the next session
471
+ * needs to know.
507
472
  */
508
- declare function GuideTour({ notes, dismissed, onDismiss }: GuideTourProps): React.JSX.Element | null;
473
+ type Text = string | Partial<Record<Lang, string>>;
474
+ declare function text(value: Text | undefined, lang: Lang): string;
475
+ /** True when a sentence exists in one language only. */
476
+ declare function untranslated(value: Text | undefined): boolean;
477
+ /**
478
+ * Writing replaces the language being typed and drops the other copy of that
479
+ * field. Deliberate: a translation of a sentence somebody just rewrote is a
480
+ * lie about what the card says, and the gap is the panel telling the next
481
+ * session what to fill in.
482
+ */
483
+ declare function writeText(lang: Lang, value: string): Text;
509
484
 
510
- type NotificationTone = 'info' | 'attention';
511
- interface PanelNotification {
512
- /**
513
- * Stable for the life of the event. Two different events must never share
514
- * one, or marking the first seen silences the second; the same event must
515
- * never change it, or it comes back every time the page loads.
516
- */
485
+ type Weight = 'primary' | 'secondary' | 'off';
486
+ type Severity = 'high' | 'medium' | 'low';
487
+ type SuggestionStatus = 'new' | 'doing' | 'done' | 'dismissed';
488
+ interface Goal {
517
489
  id: string;
518
- title: React.ReactNode;
519
- detail?: React.ReactNode;
520
- /** Shown as given, so the caller decides the format its readers expect. */
521
- at?: string;
522
- /** `attention` for something waiting on the reader, `info` for the rest. */
523
- tone?: NotificationTone;
524
- /** Where this leads. Called before the notification is marked seen. */
525
- onOpen?: () => void;
490
+ label: Text;
491
+ weight: Weight;
526
492
  }
527
- interface NotificationLabels {
528
- /** Names the control for a screen reader and on hover. */
529
- title: string;
530
- empty: string;
531
- markAll: string;
532
- /** Given the count, e.g. (n) => `${n} unread`. */
533
- unread: (count: number) => string;
493
+ interface Constraint {
494
+ id: string;
495
+ label: Text;
496
+ on: boolean;
534
497
  }
535
- interface NotificationBellProps {
536
- notifications: PanelNotification[];
537
- labels?: Partial<NotificationLabels>;
538
- /**
539
- * What this reader has already seen, when the caller keeps it somewhere
540
- * durable. Leave it out and the bell remembers in localStorage, which is the
541
- * right home for a fact about one browser.
542
- *
543
- * A panel with one password has one owner, and there "this reader" and "this
544
- * project" are the same person — so a panel that saves its board can keep
545
- * this in it and have it survive a new browser. That is the caller's call to
546
- * make, not the component's, which is why both work.
547
- */
548
- seen?: string[];
549
- /**
550
- * Told which ids were just seen. Required for `seen` to mean anything —
551
- * though the bell also remembers in localStorage either way, so a caller
552
- * that has not saved yet does not show the same notifications again.
553
- */
554
- onSeen?: (ids: string[]) => void;
498
+ interface Reading {
499
+ date: string;
500
+ value: number;
501
+ note?: Text;
555
502
  }
556
- declare function NotificationBell({ notifications, labels, seen: given, onSeen }: NotificationBellProps): React.JSX.Element | null;
557
-
558
- /**
559
- * A block of text whose whole purpose is to end up somewhere else.
560
- *
561
- * Anything a panel tells someone to paste — a settings fragment, two commands,
562
- * a shell line — is read once and copied. So the copy is the primary action
563
- * and the text is selectable underneath it, rather than the other way round.
564
- *
565
- * The clipboard API is not always there. It needs a secure context and can be
566
- * refused outright, and a copy button that silently does nothing is worse than
567
- * no button at all: the reader walks away believing they have the text. So the
568
- * failure falls back to selecting the block, and says which of the two
569
- * happened.
570
- */
571
- interface CopyBlockProps {
572
- /** Exactly what lands on the clipboard. Shown verbatim. */
573
- text: string;
574
- /** What this block is, above it. */
575
- label?: React.ReactNode;
576
- /** Why the reader wants it, and anything they must know before pasting. */
577
- note?: React.ReactNode;
578
- /** Wording, so the kit stays language-agnostic. */
579
- labels?: Partial<CopyBlockLabels>;
503
+ interface Metric {
504
+ id: string;
505
+ label: Text;
506
+ unit: string;
507
+ /** Which direction counts as better. A reading against it is what "drifting" means. */
508
+ goal: 'up' | 'down';
509
+ readings: Reading[];
580
510
  }
581
- interface CopyBlockLabels {
582
- copy: string;
583
- copied: string;
584
- /** Used when the clipboard refused and the text was selected instead. */
585
- selected: string;
511
+ interface Strategy {
512
+ id: string;
513
+ title: Text;
514
+ thesis: Text;
515
+ horizon: Text;
516
+ /** 0–10, so three bars can be compared at a glance rather than read. */
517
+ effort: number;
518
+ risk: number;
519
+ upside: number;
520
+ moves: Text[];
521
+ }
522
+ interface Suggestion {
523
+ id: string;
524
+ area: string;
525
+ severity: Severity;
526
+ status: SuggestionStatus;
527
+ date: string;
528
+ text: Text;
586
529
  }
587
- declare function CopyBlock({ text, label, note, labels }: CopyBlockProps): React.JSX.Element;
588
-
589
530
  /**
590
- * What the panel needs in order to work, and what to do about what is missing.
531
+ * A question the owner answers and Claude reads.
591
532
  *
592
- * A panel with no token does not look broken — it looks fine until the moment
593
- * someone presses Save, and then it fails with something the person reading it
594
- * did not cause and cannot place. This turns that into a list they can read
595
- * before it happens, and act on without leaving the panel.
533
+ * This is the whole point of the analysis view. A board says what to do; a
534
+ * choice says how the project wants it done, in the owner's words, once —
535
+ * instead of being asked again on every card. Claude reads these before
536
+ * deciding anything, so answering one here is worth more than answering it
537
+ * in a message that scrolls away.
596
538
  *
597
- * The status comes from the server, which is the only place that can see the
598
- * environment. It reports *whether* a variable is set and never its value, so
599
- * this component has nothing worth leaking.
539
+ * `value` of null is not missing data. It means nobody has decided, which is
540
+ * information: Claude must not guess an answer to a question the owner has
541
+ * deliberately left open.
600
542
  */
601
- type SetupStatus = 'ok' | 'missing' | 'unknown';
602
- interface SetupRequirement {
603
- id: string;
604
- /** What it is, in the reader's terms rather than the variable's. */
605
- label: React.ReactNode;
606
- status: SetupStatus;
607
- /** The environment variable behind it, when there is one to name. */
608
- variable?: string;
609
- /** What it lets the panel do — so a missing one has a visible cost. */
610
- enables?: React.ReactNode;
611
- /** Exactly what to do, shown only when it is missing. */
612
- fix?: React.ReactNode;
613
- /**
614
- * Information rather than a gate: drawn in the list, never walked as a step.
615
- *
616
- * Every other requirement here is an environment variable, and the
617
- * walkthrough's one action — re-read the environment from the server —
618
- * is what clears it. A requirement that action cannot clear parks the
619
- * walkthrough on it for ever, and because `unknown` is judged across the
620
- * whole list, one advisory row that nobody wired made every step answer a
621
- * successful check with "the panel could not ask its own server". That is
622
- * what the copied gate did the day it was added.
623
- */
624
- advisory?: boolean;
625
- }
626
- interface SetupGuideProps {
627
- requirements: SetupRequirement[];
628
- /** Shown when everything is in place, instead of an empty list. */
629
- readyMessage?: React.ReactNode;
630
- /** Wording, so the kit stays language-agnostic. */
631
- labels?: Partial<{
632
- ok: string;
633
- missing: string;
634
- unknown: string;
635
- whatToDo: string;
636
- }>;
637
- }
638
- declare function SetupGuide({ requirements, readyMessage, labels }: SetupGuideProps): React.JSX.Element;
639
-
640
- /**
641
- * The setup, walked one step at a time.
642
- *
643
- * `SetupGuide` is the list: the right shape for somebody auditing a panel that
644
- * already works. This is the other reader — the one who installed it twenty
645
- * minutes ago, has four things missing and no idea which to do first. A wall
646
- * of four is not four times as useful as one; it is the thing people close.
647
- *
648
- * Two rules hold it honest. It **checks rather than asks**: every step ends in
649
- * a button that re-reads the environment from the server, so the reader is
650
- * told whether it worked instead of ticking a box themselves. And it **never
651
- * claims what it cannot see**: a panel that could not reach its own endpoint
652
- * says the check failed, never that a variable is missing, because telling
653
- * somebody to add a token they already added is the worse of the two wrong
654
- * answers.
655
- *
656
- * There is no copy of the values here and nothing is written: the panel cannot
657
- * set an environment variable, and a button that appeared to would be the same
658
- * fault as one that appeared to install an update.
659
- */
660
- interface SetupWizardLabels {
661
- title: string;
662
- /** `(index, total)` — "Step 2 of 5". */
663
- progress: (index: number, total: number) => string;
664
- /** Shown above the one step in hand. */
665
- nowDo: string;
666
- /** The button that re-reads the environment. */
667
- check: string;
668
- checking: string;
669
- /** Said when a check ran and the step is still not in place. */
670
- stillMissing: string;
671
- /** Said when the panel could not ask its own server. */
672
- cannotTell: string;
673
- /** The heading over the finished steps. */
674
- doneTitle: string;
675
- /** Shown in place of a step when every requirement is in place. */
676
- readyTitle: string;
677
- readyBody: string;
678
- /** The two things to try before trusting the gate, as list items. */
679
- verify: string[];
680
- /** Closes the wizard. */
681
- close: string;
682
- /** The escape hatch: heading, explanation, and the prompt to copy. */
683
- stuckTitle: string;
684
- stuckBody: string;
685
- stuckPrompt: string;
686
- copy: string;
687
- copied: string;
688
- }
689
- interface SetupWizardProps {
690
- /** In the order they are to be done. The caller owns that order. */
691
- requirements: SetupRequirement[];
692
- labels: SetupWizardLabels;
693
- /** Re-reads the environment. Resolves when the answer has been applied. */
694
- onCheck: () => Promise<void> | void;
695
- /** Told when the reader closes it. */
696
- onClose?: () => void;
697
- }
698
- declare function SetupWizard({ requirements, labels, onCheck, onClose }: SetupWizardProps): React.JSX.Element;
699
-
700
- /**
701
- * Where a reader is in the setup, as one answer instead of a list to read.
702
- *
703
- * `SetupGuide` draws every requirement at once, which is the right shape for
704
- * somebody checking a panel that already works. It is the wrong shape for
705
- * somebody who has just installed one: five things at once, four of them
706
- * missing, and no order to do them in reads as a wall rather than as a first
707
- * step. So the same requirements are walked one at a time, and this is the
708
- * function that decides which one.
709
- *
710
- * The order the caller gives is the order they are done in — a token that
711
- * cannot be scoped before the repository is named is a step in the wrong
712
- * place, and that is the caller's knowledge, not this function's.
713
- */
714
- interface WizardStep {
715
- /** The requirement to show, or null when nothing is left to do. */
716
- current: SetupRequirement | null;
717
- /** 1-based, for "step 2 of 5". Zero when there is nothing left. */
718
- index: number;
719
- /** How many steps this setup has in total. */
720
- total: number;
721
- /** Every requirement that is in place, in the caller's order. */
722
- done: SetupRequirement[];
723
- /** True when every requirement is `ok`. */
724
- ready: boolean;
725
- /**
726
- * True when the panel could not ask the server, so nothing is claimed.
727
- *
728
- * This is not "not ready": a panel that cannot reach its own endpoint knows
729
- * nothing about the environment, and telling somebody to add a token they
730
- * already added is worse than saying the check itself failed.
731
- */
732
- unknown: boolean;
733
- }
734
- declare function wizardStep(requirements: SetupRequirement[]): WizardStep;
735
- /**
736
- * Whether this panel has never been worked, which is what "first install"
737
- * means here.
738
- *
739
- * `paneltir init` writes a board with the columns already there and nothing
740
- * on them, so a board carrying no cards and no runs is one nobody has used
741
- * yet. That is a better signal than a version on disk: a project can install
742
- * the package, never open the panel, and update three times before anyone
743
- * sees it — and it is still that reader's first run.
744
- *
745
- * A panel that has been worked and then loses a variable is deliberately not
746
- * this. It needs the same instructions and gets them from the same wizard,
747
- * but it is told rather than taken over: somebody mid-sentence on a board
748
- * they know does not want a walkthrough opening on top of it.
749
- */
750
- declare function isFirstRun(board: {
751
- cards?: unknown[];
752
- runs?: unknown[];
753
- }): boolean;
754
-
755
- type Handler = (payload: Record<string, string>, event: MouseEvent) => void;
756
- /**
757
- * A single click listener on document that dispatches by data-* attributes,
758
- * instead of one handler per element. Meant for a project's own actions; the
759
- * Board does not use it internally.
760
- *
761
- * Example: useDelegatedClick('data-action', (payload) => { if (payload.action === 'delete') ... })
762
- * with buttons such as <button data-action="delete" data-id="42">
763
- */
764
- declare function useDelegatedClick(attribute: string, handler: Handler): void;
765
-
766
- declare function useMediaQuery(query: string): boolean;
767
-
768
- /**
769
- * A board is read in one language and written in another.
770
- *
771
- * This is the mechanism only — the types and the resolution. The panel's own
772
- * wording lives with the panel: a library that shipped its copy would be a
773
- * library deciding what a project's board says.
774
- */
775
- type Lang = 'en' | 'es';
776
- declare const LANGS: Lang[];
777
- declare const LANG_LABELS: Record<Lang, string>;
778
- /**
779
- * A string, or the same string per language.
780
- *
781
- * A plain string is a claim: this text is right in either language — true for
782
- * a product name, a number, a file id, and almost never for prose. An object
783
- * is the honest shape for a sentence, and one that carries a single language
784
- * says "written, not translated yet", which is exactly what the next session
785
- * needs to know.
786
- */
787
- type Text = string | Partial<Record<Lang, string>>;
788
- declare function text(value: Text | undefined, lang: Lang): string;
789
- /** True when a sentence exists in one language only. */
790
- declare function untranslated(value: Text | undefined): boolean;
791
- /**
792
- * Writing replaces the language being typed and drops the other copy of that
793
- * field. Deliberate: a translation of a sentence somebody just rewrote is a
794
- * lie about what the card says, and the gap is the panel telling the next
795
- * session what to fill in.
796
- */
797
- declare function writeText(lang: Lang, value: string): Text;
798
-
799
- type Weight = 'primary' | 'secondary' | 'off';
800
- type Severity = 'high' | 'medium' | 'low';
801
- type SuggestionStatus = 'new' | 'doing' | 'done' | 'dismissed';
802
- interface Goal {
803
- id: string;
804
- label: Text;
805
- weight: Weight;
806
- }
807
- interface Constraint {
808
- id: string;
809
- label: Text;
810
- on: boolean;
811
- }
812
- interface Reading {
813
- date: string;
814
- value: number;
815
- note?: Text;
816
- }
817
- interface Metric {
818
- id: string;
819
- label: Text;
820
- unit: string;
821
- /** Which direction counts as better. A reading against it is what "drifting" means. */
822
- goal: 'up' | 'down';
823
- readings: Reading[];
824
- }
825
- interface Strategy {
826
- id: string;
827
- title: Text;
828
- thesis: Text;
829
- horizon: Text;
830
- /** 0–10, so three bars can be compared at a glance rather than read. */
831
- effort: number;
832
- risk: number;
833
- upside: number;
834
- moves: Text[];
835
- }
836
- interface Suggestion {
837
- id: string;
838
- area: string;
839
- severity: Severity;
840
- status: SuggestionStatus;
841
- date: string;
842
- text: Text;
843
- }
844
- /**
845
- * A question the owner answers and Claude reads.
846
- *
847
- * This is the whole point of the analysis view. A board says what to do; a
848
- * choice says how the project wants it done, in the owner's words, once —
849
- * instead of being asked again on every card. Claude reads these before
850
- * deciding anything, so answering one here is worth more than answering it
851
- * in a message that scrolls away.
852
- *
853
- * `value` of null is not missing data. It means nobody has decided, which is
854
- * information: Claude must not guess an answer to a question the owner has
855
- * deliberately left open.
856
- */
857
- interface Choice {
543
+ interface Choice {
858
544
  id: string;
859
545
  question: Text;
860
546
  /** What actually changes depending on the answer. Never "choose an option". */
@@ -1050,148 +736,540 @@ interface Run {
1050
736
  /** The kit fingerprint at the time, twelve characters is enough to compare. */
1051
737
  fingerprint?: string;
1052
738
  }
1053
- interface HealthFact {
1054
- label: Text;
1055
- value: Text;
1056
- status: 'ok' | 'warning' | 'bad' | 'unknown';
739
+ interface HealthFact {
740
+ label: Text;
741
+ value: Text;
742
+ status: 'ok' | 'warning' | 'bad' | 'unknown';
743
+ }
744
+ /**
745
+ * A theme Claude derived from the project it is installed in.
746
+ *
747
+ * Stored with where it came from, not only with its colours. "Update the
748
+ * theme" is only a meaningful request if the next answer can be compared with
749
+ * this one — otherwise a regenerated theme is a different theme for reasons
750
+ * nobody can see. `from` is what makes the difference readable.
751
+ *
752
+ * `refreshRequested` is a message, not a trigger: a panel cannot run Claude.
753
+ * Tapping Update writes the date here, the bell shows it, and the next session
754
+ * regenerates. Pretending the button does the work itself would leave the
755
+ * owner waiting for something that was never going to happen.
756
+ */
757
+ interface ImportedTheme {
758
+ /** What to call it in the theme list. */
759
+ label: Text;
760
+ tokens: Record<string, string>;
761
+ form?: Record<string, string | number>;
762
+ from: {
763
+ /** Files the colours were read out of, so the claim can be checked. */
764
+ files: string[];
765
+ /** The colours actually found, before they were mapped to tokens. */
766
+ colours: string[];
767
+ /** Why these tokens and not others. One or two sentences. */
768
+ reasoning: Text;
769
+ };
770
+ importedAt: string;
771
+ importedBy: 'claude';
772
+ /** Set by the panel when the owner asks for it to be derived again. */
773
+ refreshRequested?: string | null;
774
+ }
775
+ /**
776
+ * What the owner has chosen, kept in the board so it survives a new browser.
777
+ *
778
+ * The kit's default is to remember these per browser, in localStorage, because
779
+ * for a component "which notes this reader dismissed" is a fact about a reader
780
+ * and not about a project. A panel with one password is the case where those
781
+ * two are the same person: keeping it here means the theme, the language and
782
+ * what has already been read follow the owner to a new machine instead of
783
+ * greeting them with a tour they finished months ago.
784
+ *
785
+ * The cost is real and worth naming: anyone who shares the password shares
786
+ * this. It holds a theme name and a list of ids — nothing worth stealing, and
787
+ * nothing anyone else would want.
788
+ */
789
+ interface Preferences {
790
+ theme?: string;
791
+ lang?: Lang;
792
+ /** Ids of notifications already read. Bounded when saved. */
793
+ seen?: string[];
794
+ /** Ids of guide notes already dismissed. */
795
+ guideDismissed?: string[];
796
+ /**
797
+ * Whether an update card is written as an order rather than a suggestion.
798
+ *
799
+ * It does not make anything automatic — the panel cannot run npm, and this
800
+ * changes `order` on the card it offers to write, nothing else. Off means
801
+ * the card asks; on means it tells.
802
+ */
803
+ updateAsOrder?: boolean;
804
+ }
805
+ interface PanelState {
806
+ v: 2;
807
+ project: string;
808
+ environment: Text;
809
+ updatedAt: string;
810
+ notice?: Text;
811
+ columns: ColumnDef[];
812
+ areas: {
813
+ id: string;
814
+ label: Text;
815
+ }[];
816
+ health: HealthFact[];
817
+ cards: Card[];
818
+ /** Newest first. Capped when saving, so the file cannot grow without end. */
819
+ runs: Run[];
820
+ /** What any of the work is for: goals, constraints, metrics, strategies. */
821
+ analysis?: Analysis;
822
+ /** The owner's choices, so they survive a new browser. */
823
+ preferences?: Preferences;
824
+ /** A theme Claude derived from this project, with where it came from. */
825
+ importedTheme?: ImportedTheme;
826
+ }
827
+ declare const LEVELS: Level[];
828
+ declare const OWNERS: Owner[];
829
+ declare function newId(): string;
830
+ declare function today(): string;
831
+ /** A card is created empty and named second — the same as tapping "+" on paper. */
832
+ declare function emptyCard(column: string, area: string): Card;
833
+ /**
834
+ * How long a finished card stays on the board before it becomes history.
835
+ *
836
+ * A done column only grows. It is the one column nothing ever removes from,
837
+ * so a board that is being worked turns into a wall of things already
838
+ * finished, and the three columns that need reading get a third of the screen
839
+ * between them. A week is the window in which "what did we ship" is still a
840
+ * live question; after that it is a record, and a record does not need to be
841
+ * in the way.
842
+ */
843
+ declare const ARCHIVE_AFTER_DAYS = 7;
844
+ /**
845
+ * Whether a finished card has been finished long enough to stop being drawn.
846
+ *
847
+ * Derived on every render and never written, which is the arrangement
848
+ * `boardFacts()` already uses: a flag stored on the card would be wrong the
849
+ * day after it was set, and setting it would mark the panel dirty for the
850
+ * passage of time. Here the answer simply becomes true on its own.
851
+ *
852
+ * **A card with no `completedAt` is never archived**, whatever column it sits
853
+ * in. It reached done without being stamped — a board edited by hand, or one
854
+ * from before the stamp existed — so its age is not known, and hiding a card
855
+ * whose age nobody knows is the same mistake as reporting a token missing
856
+ * because the check could not run. `unknown` is not `old`.
857
+ */
858
+ declare function isArchived(card: Card, doneColumns: ReadonlySet<string>, now?: string, afterDays?: number): boolean;
859
+ /**
860
+ * Applies the column's own meaning to a card that just moved into it: a card
861
+ * entering the working column is started, one reaching a done column is
862
+ * finished, and one dragged back out of done is not finished any more.
863
+ */
864
+ declare function stampForColumn(card: Card, columns: ColumnDef[]): Card;
865
+ /**
866
+ * Where a card the panel writes on the reader's behalf goes.
867
+ *
868
+ * The update card and the four "ask for this again" cards used to be born in
869
+ * a column literally called `next`. The default board has one; a board whose
870
+ * owner renamed it does not, and a card in a column that does not exist is
871
+ * the exact fault `validateBoard` names — drawn nowhere, and refused by the
872
+ * write path, so one tap on a button turned a saveable board into one that
873
+ * could not be saved and said so about a column the reader had never heard of.
874
+ *
875
+ * `next` when there is one, because that is what the button means. Otherwise
876
+ * the first column that is neither in flight nor finished nor the owner's:
877
+ * queued work goes where queued work already sits. And the first column of
878
+ * all when nothing else fits, since a board with no columns cannot pass the
879
+ * validator either way.
880
+ */
881
+ declare function queueColumn(columns: ColumnDef[]): string;
882
+ /** A card claiming to be finished that was never picked up. */
883
+ declare function claimedWithoutStarting(card: Card): boolean;
884
+ interface Counts {
885
+ open: number;
886
+ orders: number;
887
+ yours: number;
888
+ decisions: number;
889
+ highRisk: number;
890
+ done: number;
891
+ }
892
+ declare function countCards(state: PanelState): Counts;
893
+ /** How many checklist steps are ticked, for the counter on a card. */
894
+ declare function checkProgress(card: Card): {
895
+ done: number;
896
+ total: number;
897
+ } | undefined;
898
+
899
+ /**
900
+ * Cards the panel writes when the owner asks for something it cannot do.
901
+ *
902
+ * The panel runs nothing. It cannot bump a version, install a plugin or
903
+ * produce a market report, and a button that appeared to would be the fault
904
+ * this project keeps refusing. What it can do is write the request down where
905
+ * work reaches Claude — an order card on the board — and open it, so the tap
906
+ * is visibly a request and the next session carries it out.
907
+ *
908
+ * Two rules live here rather than in the component, because they are the part
909
+ * with decisions in it and a component cannot be tested in this repository:
910
+ *
911
+ * - **One open request per subject.** A second tap while the first is still
912
+ * waiting opens the card already asked for. Two version bumps queued at
913
+ * once is a project two versions ahead of where anybody meant, and two
914
+ * install cards for one tool is the same edit made twice.
915
+ * - **A finished request does not block a new one.** Once the card reaches a
916
+ * done column the subject is free again: next week's bump is a new card.
917
+ */
918
+
919
+ /** The kinds of request the panel writes. */
920
+ type RequestKind = 'version-bump' | 'install';
921
+ /** `version-bump-2026-09-26`, `install-deploybudget-2026-09-26`. */
922
+ declare function requestId(kind: RequestKind, day: string, subject?: string): string;
923
+ /**
924
+ * The request of this kind, for this subject, still waiting on the board.
925
+ *
926
+ * "Waiting" is anything outside a done column — queued, in flight, or parked
927
+ * in the owner's column with a question on it. Only finishing it frees the
928
+ * subject for a new request.
929
+ */
930
+ declare function pendingRequest(cards: Card[], kind: RequestKind, doneColumns: ReadonlySet<string>, subject?: string): Card | undefined;
931
+
932
+ interface DetailSheetProps {
933
+ open: boolean;
934
+ onClose: () => void;
935
+ children: React.ReactNode;
936
+ ariaLabel?: string;
937
+ }
938
+ /**
939
+ * Below 760px this is a sheet rising from the bottom; from 760px up the same
940
+ * markup becomes a centred floating dialog. The breakpoint is handled in CSS,
941
+ * so there are not two different components.
942
+ */
943
+ declare function DetailSheet({ open, onClose, children, ariaLabel }: DetailSheetProps): React.JSX.Element | null;
944
+ interface DetailSectionProps {
945
+ title: string;
946
+ /** Clarifying phrase in lowercase, printed after an em dash. */
947
+ subtitle?: string;
948
+ children: React.ReactNode;
949
+ }
950
+ declare function DetailSection({ title, subtitle, children }: DetailSectionProps): React.JSX.Element;
951
+
952
+ /**
953
+ * Whether a note has been dismissed, and how to dismiss it.
954
+ *
955
+ * Starts as "shown but not yet decided" so the first paint matches the server
956
+ * and nothing flashes into place; the stored answer arrives on the first
957
+ * effect.
958
+ */
959
+ declare function useGuideNote(id: string, remember?: boolean): {
960
+ visible: boolean;
961
+ dismiss: () => void;
962
+ };
963
+ /** Clears every dismissal, so the guidance can be asked for again. */
964
+ declare function resetGuide(): void;
965
+ interface GuideNoteProps {
966
+ /** Stable across releases: changing it makes the note reappear for everyone. */
967
+ id: string;
968
+ title: React.ReactNode;
969
+ children: React.ReactNode;
970
+ /** Wording of the dismiss button. Say what it does, not "OK". */
971
+ dismissLabel?: string;
972
+ /** Shown after the note, for the one thing it wants the reader to do. */
973
+ action?: {
974
+ label: string;
975
+ onClick: () => void;
976
+ };
977
+ /** Position in a sequence, so a first session reads as a tour, not a pile. */
978
+ step?: {
979
+ index: number;
980
+ total: number;
981
+ };
982
+ /** Told after the note is dismissed, so a sequence can move on. */
983
+ onDismissed?: () => void;
984
+ /**
985
+ * Whether to record the dismissal in localStorage. False when the caller
986
+ * keeps that answer itself, so the two cannot disagree.
987
+ */
988
+ remember?: boolean;
989
+ }
990
+ declare function GuideNote({ id, title, children, dismissLabel, action, step, onDismissed, remember, }: GuideNoteProps): React.JSX.Element | null;
991
+ interface GuideTourProps {
992
+ /** Notes in order. Only the first undismissed one shows, so it reads as a tour. */
993
+ notes: Array<Omit<GuideNoteProps, 'step' | 'onDismissed'>>;
994
+ /**
995
+ * Which notes are already dismissed, when the caller keeps that somewhere
996
+ * durable. Leave it out and each reader's browser remembers, which is the
997
+ * right home for a fact about one browser — and the wrong one for a panel
998
+ * with a single owner, who would be told the same tour again on a new
999
+ * machine. Passing it does not switch the browser off: a note dismissed
1000
+ * here is written to both, because a caller whose store needs saving would
1001
+ * otherwise lose the dismissal on reload.
1002
+ */
1003
+ dismissed?: string[];
1004
+ /** Told when a note is dismissed. Required for `dismissed` to mean anything. */
1005
+ onDismiss?: (id: string) => void;
1006
+ }
1007
+ /**
1008
+ * One note at a time, in order.
1009
+ *
1010
+ * Five notes at once is a wall nobody reads. Shown one at a time, dismissing
1011
+ * each reveals the next, and a reader who dismisses all of them has been
1012
+ * through the tour rather than having closed a box.
1013
+ */
1014
+ declare function GuideTour({ notes, dismissed, onDismiss }: GuideTourProps): React.JSX.Element | null;
1015
+
1016
+ type NotificationTone = 'info' | 'attention';
1017
+ interface PanelNotification {
1018
+ /**
1019
+ * Stable for the life of the event. Two different events must never share
1020
+ * one, or marking the first seen silences the second; the same event must
1021
+ * never change it, or it comes back every time the page loads.
1022
+ */
1023
+ id: string;
1024
+ title: React.ReactNode;
1025
+ detail?: React.ReactNode;
1026
+ /** Shown as given, so the caller decides the format its readers expect. */
1027
+ at?: string;
1028
+ /** `attention` for something waiting on the reader, `info` for the rest. */
1029
+ tone?: NotificationTone;
1030
+ /** Where this leads. Called before the notification is marked seen. */
1031
+ onOpen?: () => void;
1032
+ }
1033
+ interface NotificationLabels {
1034
+ /** Names the control for a screen reader and on hover. */
1035
+ title: string;
1036
+ empty: string;
1037
+ markAll: string;
1038
+ /** Given the count, e.g. (n) => `${n} unread`. */
1039
+ unread: (count: number) => string;
1040
+ }
1041
+ interface NotificationBellProps {
1042
+ notifications: PanelNotification[];
1043
+ labels?: Partial<NotificationLabels>;
1044
+ /**
1045
+ * What this reader has already seen, when the caller keeps it somewhere
1046
+ * durable. Leave it out and the bell remembers in localStorage, which is the
1047
+ * right home for a fact about one browser.
1048
+ *
1049
+ * A panel with one password has one owner, and there "this reader" and "this
1050
+ * project" are the same person — so a panel that saves its board can keep
1051
+ * this in it and have it survive a new browser. That is the caller's call to
1052
+ * make, not the component's, which is why both work.
1053
+ */
1054
+ seen?: string[];
1055
+ /**
1056
+ * Told which ids were just seen. Required for `seen` to mean anything —
1057
+ * though the bell also remembers in localStorage either way, so a caller
1058
+ * that has not saved yet does not show the same notifications again.
1059
+ */
1060
+ onSeen?: (ids: string[]) => void;
1061
+ }
1062
+ declare function NotificationBell({ notifications, labels, seen: given, onSeen }: NotificationBellProps): React.JSX.Element | null;
1063
+
1064
+ /**
1065
+ * A block of text whose whole purpose is to end up somewhere else.
1066
+ *
1067
+ * Anything a panel tells someone to paste — a settings fragment, two commands,
1068
+ * a shell line — is read once and copied. So the copy is the primary action
1069
+ * and the text is selectable underneath it, rather than the other way round.
1070
+ *
1071
+ * The clipboard API is not always there. It needs a secure context and can be
1072
+ * refused outright, and a copy button that silently does nothing is worse than
1073
+ * no button at all: the reader walks away believing they have the text. So the
1074
+ * failure falls back to selecting the block, and says which of the two
1075
+ * happened.
1076
+ */
1077
+ interface CopyBlockProps {
1078
+ /** Exactly what lands on the clipboard. Shown verbatim. */
1079
+ text: string;
1080
+ /** What this block is, above it. */
1081
+ label?: React.ReactNode;
1082
+ /** Why the reader wants it, and anything they must know before pasting. */
1083
+ note?: React.ReactNode;
1084
+ /** Wording, so the kit stays language-agnostic. */
1085
+ labels?: Partial<CopyBlockLabels>;
1086
+ }
1087
+ interface CopyBlockLabels {
1088
+ copy: string;
1089
+ copied: string;
1090
+ /** Used when the clipboard refused and the text was selected instead. */
1091
+ selected: string;
1092
+ }
1093
+ declare function CopyBlock({ text, label, note, labels }: CopyBlockProps): React.JSX.Element;
1094
+
1095
+ /**
1096
+ * What the panel needs in order to work, and what to do about what is missing.
1097
+ *
1098
+ * A panel with no token does not look broken — it looks fine until the moment
1099
+ * someone presses Save, and then it fails with something the person reading it
1100
+ * did not cause and cannot place. This turns that into a list they can read
1101
+ * before it happens, and act on without leaving the panel.
1102
+ *
1103
+ * The status comes from the server, which is the only place that can see the
1104
+ * environment. It reports *whether* a variable is set and never its value, so
1105
+ * this component has nothing worth leaking.
1106
+ */
1107
+ type SetupStatus = 'ok' | 'missing' | 'unknown';
1108
+ interface SetupRequirement {
1109
+ id: string;
1110
+ /** What it is, in the reader's terms rather than the variable's. */
1111
+ label: React.ReactNode;
1112
+ status: SetupStatus;
1113
+ /** The environment variable behind it, when there is one to name. */
1114
+ variable?: string;
1115
+ /** What it lets the panel do — so a missing one has a visible cost. */
1116
+ enables?: React.ReactNode;
1117
+ /** Exactly what to do, shown only when it is missing. */
1118
+ fix?: React.ReactNode;
1119
+ /**
1120
+ * Information rather than a gate: drawn in the list, never walked as a step.
1121
+ *
1122
+ * Every other requirement here is an environment variable, and the
1123
+ * walkthrough's one action — re-read the environment from the server —
1124
+ * is what clears it. A requirement that action cannot clear parks the
1125
+ * walkthrough on it for ever, and because `unknown` is judged across the
1126
+ * whole list, one advisory row that nobody wired made every step answer a
1127
+ * successful check with "the panel could not ask its own server". That is
1128
+ * what the copied gate did the day it was added.
1129
+ */
1130
+ advisory?: boolean;
1131
+ }
1132
+ interface SetupGuideProps {
1133
+ requirements: SetupRequirement[];
1134
+ /** Shown when everything is in place, instead of an empty list. */
1135
+ readyMessage?: React.ReactNode;
1136
+ /** Wording, so the kit stays language-agnostic. */
1137
+ labels?: Partial<{
1138
+ ok: string;
1139
+ missing: string;
1140
+ unknown: string;
1141
+ whatToDo: string;
1142
+ }>;
1057
1143
  }
1144
+ declare function SetupGuide({ requirements, readyMessage, labels }: SetupGuideProps): React.JSX.Element;
1145
+
1058
1146
  /**
1059
- * A theme Claude derived from the project it is installed in.
1147
+ * The setup, walked one step at a time.
1060
1148
  *
1061
- * Stored with where it came from, not only with its colours. "Update the
1062
- * theme" is only a meaningful request if the next answer can be compared with
1063
- * this one — otherwise a regenerated theme is a different theme for reasons
1064
- * nobody can see. `from` is what makes the difference readable.
1149
+ * `SetupGuide` is the list: the right shape for somebody auditing a panel that
1150
+ * already works. This is the other reader — the one who installed it twenty
1151
+ * minutes ago, has four things missing and no idea which to do first. A wall
1152
+ * of four is not four times as useful as one; it is the thing people close.
1065
1153
  *
1066
- * `refreshRequested` is a message, not a trigger: a panel cannot run Claude.
1067
- * Tapping Update writes the date here, the bell shows it, and the next session
1068
- * regenerates. Pretending the button does the work itself would leave the
1069
- * owner waiting for something that was never going to happen.
1154
+ * Two rules hold it honest. It **checks rather than asks**: every step ends in
1155
+ * a button that re-reads the environment from the server, so the reader is
1156
+ * told whether it worked instead of ticking a box themselves. And it **never
1157
+ * claims what it cannot see**: a panel that could not reach its own endpoint
1158
+ * says the check failed, never that a variable is missing, because telling
1159
+ * somebody to add a token they already added is the worse of the two wrong
1160
+ * answers.
1161
+ *
1162
+ * There is no copy of the values here and nothing is written: the panel cannot
1163
+ * set an environment variable, and a button that appeared to would be the same
1164
+ * fault as one that appeared to install an update.
1070
1165
  */
1071
- interface ImportedTheme {
1072
- /** What to call it in the theme list. */
1073
- label: Text;
1074
- tokens: Record<string, string>;
1075
- form?: Record<string, string | number>;
1076
- from: {
1077
- /** Files the colours were read out of, so the claim can be checked. */
1078
- files: string[];
1079
- /** The colours actually found, before they were mapped to tokens. */
1080
- colours: string[];
1081
- /** Why these tokens and not others. One or two sentences. */
1082
- reasoning: Text;
1083
- };
1084
- importedAt: string;
1085
- importedBy: 'claude';
1086
- /** Set by the panel when the owner asks for it to be derived again. */
1087
- refreshRequested?: string | null;
1166
+ interface SetupWizardLabels {
1167
+ title: string;
1168
+ /** `(index, total)` — "Step 2 of 5". */
1169
+ progress: (index: number, total: number) => string;
1170
+ /** Shown above the one step in hand. */
1171
+ nowDo: string;
1172
+ /** The button that re-reads the environment. */
1173
+ check: string;
1174
+ checking: string;
1175
+ /** Said when a check ran and the step is still not in place. */
1176
+ stillMissing: string;
1177
+ /** Said when the panel could not ask its own server. */
1178
+ cannotTell: string;
1179
+ /** The heading over the finished steps. */
1180
+ doneTitle: string;
1181
+ /** Shown in place of a step when every requirement is in place. */
1182
+ readyTitle: string;
1183
+ readyBody: string;
1184
+ /** The two things to try before trusting the gate, as list items. */
1185
+ verify: string[];
1186
+ /** Closes the wizard. */
1187
+ close: string;
1188
+ /** The escape hatch: heading, explanation, and the prompt to copy. */
1189
+ stuckTitle: string;
1190
+ stuckBody: string;
1191
+ stuckPrompt: string;
1192
+ copy: string;
1193
+ copied: string;
1194
+ }
1195
+ interface SetupWizardProps {
1196
+ /** In the order they are to be done. The caller owns that order. */
1197
+ requirements: SetupRequirement[];
1198
+ labels: SetupWizardLabels;
1199
+ /** Re-reads the environment. Resolves when the answer has been applied. */
1200
+ onCheck: () => Promise<void> | void;
1201
+ /** Told when the reader closes it. */
1202
+ onClose?: () => void;
1088
1203
  }
1204
+ declare function SetupWizard({ requirements, labels, onCheck, onClose }: SetupWizardProps): React.JSX.Element;
1205
+
1089
1206
  /**
1090
- * What the owner has chosen, kept in the board so it survives a new browser.
1207
+ * Where a reader is in the setup, as one answer instead of a list to read.
1091
1208
  *
1092
- * The kit's default is to remember these per browser, in localStorage, because
1093
- * for a component "which notes this reader dismissed" is a fact about a reader
1094
- * and not about a project. A panel with one password is the case where those
1095
- * two are the same person: keeping it here means the theme, the language and
1096
- * what has already been read follow the owner to a new machine instead of
1097
- * greeting them with a tour they finished months ago.
1209
+ * `SetupGuide` draws every requirement at once, which is the right shape for
1210
+ * somebody checking a panel that already works. It is the wrong shape for
1211
+ * somebody who has just installed one: five things at once, four of them
1212
+ * missing, and no order to do them in reads as a wall rather than as a first
1213
+ * step. So the same requirements are walked one at a time, and this is the
1214
+ * function that decides which one.
1098
1215
  *
1099
- * The cost is real and worth naming: anyone who shares the password shares
1100
- * this. It holds a theme name and a list of ids — nothing worth stealing, and
1101
- * nothing anyone else would want.
1216
+ * The order the caller gives is the order they are done in — a token that
1217
+ * cannot be scoped before the repository is named is a step in the wrong
1218
+ * place, and that is the caller's knowledge, not this function's.
1102
1219
  */
1103
- interface Preferences {
1104
- theme?: string;
1105
- lang?: Lang;
1106
- /** Ids of notifications already read. Bounded when saved. */
1107
- seen?: string[];
1108
- /** Ids of guide notes already dismissed. */
1109
- guideDismissed?: string[];
1220
+ interface WizardStep {
1221
+ /** The requirement to show, or null when nothing is left to do. */
1222
+ current: SetupRequirement | null;
1223
+ /** 1-based, for "step 2 of 5". Zero when there is nothing left. */
1224
+ index: number;
1225
+ /** How many steps this setup has in total. */
1226
+ total: number;
1227
+ /** Every requirement that is in place, in the caller's order. */
1228
+ done: SetupRequirement[];
1229
+ /** True when every requirement is `ok`. */
1230
+ ready: boolean;
1110
1231
  /**
1111
- * Whether an update card is written as an order rather than a suggestion.
1232
+ * True when the panel could not ask the server, so nothing is claimed.
1112
1233
  *
1113
- * It does not make anything automatic — the panel cannot run npm, and this
1114
- * changes `order` on the card it offers to write, nothing else. Off means
1115
- * the card asks; on means it tells.
1234
+ * This is not "not ready": a panel that cannot reach its own endpoint knows
1235
+ * nothing about the environment, and telling somebody to add a token they
1236
+ * already added is worse than saying the check itself failed.
1116
1237
  */
1117
- updateAsOrder?: boolean;
1118
- }
1119
- interface PanelState {
1120
- v: 2;
1121
- project: string;
1122
- environment: Text;
1123
- updatedAt: string;
1124
- notice?: Text;
1125
- columns: ColumnDef[];
1126
- areas: {
1127
- id: string;
1128
- label: Text;
1129
- }[];
1130
- health: HealthFact[];
1131
- cards: Card[];
1132
- /** Newest first. Capped when saving, so the file cannot grow without end. */
1133
- runs: Run[];
1134
- /** What any of the work is for: goals, constraints, metrics, strategies. */
1135
- analysis?: Analysis;
1136
- /** The owner's choices, so they survive a new browser. */
1137
- preferences?: Preferences;
1138
- /** A theme Claude derived from this project, with where it came from. */
1139
- importedTheme?: ImportedTheme;
1238
+ unknown: boolean;
1140
1239
  }
1141
- declare const LEVELS: Level[];
1142
- declare const OWNERS: Owner[];
1143
- declare function newId(): string;
1144
- declare function today(): string;
1145
- /** A card is created empty and named second — the same as tapping "+" on paper. */
1146
- declare function emptyCard(column: string, area: string): Card;
1147
- /**
1148
- * How long a finished card stays on the board before it becomes history.
1149
- *
1150
- * A done column only grows. It is the one column nothing ever removes from,
1151
- * so a board that is being worked turns into a wall of things already
1152
- * finished, and the three columns that need reading get a third of the screen
1153
- * between them. A week is the window in which "what did we ship" is still a
1154
- * live question; after that it is a record, and a record does not need to be
1155
- * in the way.
1156
- */
1157
- declare const ARCHIVE_AFTER_DAYS = 7;
1240
+ declare function wizardStep(requirements: SetupRequirement[]): WizardStep;
1158
1241
  /**
1159
- * Whether a finished card has been finished long enough to stop being drawn.
1242
+ * Whether this panel has never been worked, which is what "first install"
1243
+ * means here.
1160
1244
  *
1161
- * Derived on every render and never written, which is the arrangement
1162
- * `boardFacts()` already uses: a flag stored on the card would be wrong the
1163
- * day after it was set, and setting it would mark the panel dirty for the
1164
- * passage of time. Here the answer simply becomes true on its own.
1245
+ * `paneltir init` writes a board with the columns already there and nothing
1246
+ * on them, so a board carrying no cards and no runs is one nobody has used
1247
+ * yet. That is a better signal than a version on disk: a project can install
1248
+ * the package, never open the panel, and update three times before anyone
1249
+ * sees it — and it is still that reader's first run.
1165
1250
  *
1166
- * **A card with no `completedAt` is never archived**, whatever column it sits
1167
- * in. It reached done without being stamped — a board edited by hand, or one
1168
- * from before the stamp existed — so its age is not known, and hiding a card
1169
- * whose age nobody knows is the same mistake as reporting a token missing
1170
- * because the check could not run. `unknown` is not `old`.
1251
+ * A panel that has been worked and then loses a variable is deliberately not
1252
+ * this. It needs the same instructions and gets them from the same wizard,
1253
+ * but it is told rather than taken over: somebody mid-sentence on a board
1254
+ * they know does not want a walkthrough opening on top of it.
1171
1255
  */
1172
- declare function isArchived(card: Card, doneColumns: ReadonlySet<string>, now?: string, afterDays?: number): boolean;
1256
+ declare function isFirstRun(board: {
1257
+ cards?: unknown[];
1258
+ runs?: unknown[];
1259
+ }): boolean;
1260
+
1261
+ type Handler = (payload: Record<string, string>, event: MouseEvent) => void;
1173
1262
  /**
1174
- * Applies the column's own meaning to a card that just moved into it: a card
1175
- * entering the working column is started, one reaching a done column is
1176
- * finished, and one dragged back out of done is not finished any more.
1263
+ * A single click listener on document that dispatches by data-* attributes,
1264
+ * instead of one handler per element. Meant for a project's own actions; the
1265
+ * Board does not use it internally.
1266
+ *
1267
+ * Example: useDelegatedClick('data-action', (payload) => { if (payload.action === 'delete') ... })
1268
+ * with buttons such as <button data-action="delete" data-id="42">
1177
1269
  */
1178
- declare function stampForColumn(card: Card, columns: ColumnDef[]): Card;
1179
- /** A card claiming to be finished that was never picked up. */
1180
- declare function claimedWithoutStarting(card: Card): boolean;
1181
- interface Counts {
1182
- open: number;
1183
- orders: number;
1184
- yours: number;
1185
- decisions: number;
1186
- highRisk: number;
1187
- done: number;
1188
- }
1189
- declare function countCards(state: PanelState): Counts;
1190
- /** How many checklist steps are ticked, for the counter on a card. */
1191
- declare function checkProgress(card: Card): {
1192
- done: number;
1193
- total: number;
1194
- } | undefined;
1270
+ declare function useDelegatedClick(attribute: string, handler: Handler): void;
1271
+
1272
+ declare function useMediaQuery(query: string): boolean;
1195
1273
 
1196
1274
  /**
1197
1275
  * Reads a board and says what is wrong with it, in words.
@@ -1426,6 +1504,18 @@ interface UiStrings {
1426
1504
  releaseNone: string;
1427
1505
  updateCardTitle: (version: string) => string;
1428
1506
  updateCardBody: (version: string) => string;
1507
+ versionBumpAsk: string;
1508
+ versionBumpOpen: string;
1509
+ versionBumpHow: string;
1510
+ versionBumpTitle: string;
1511
+ versionBumpBody: string;
1512
+ versionBumpChecks: string[];
1513
+ installAsk: string;
1514
+ installAskOpen: string;
1515
+ installAskHow: string;
1516
+ installCardTitle: (tool: string) => string;
1517
+ installCardBody: (tool: string, file: string, snippet: string) => string;
1518
+ installCardChecks: string[];
1429
1519
  cardDetail: string;
1430
1520
  panelSettings: string;
1431
1521
  title: string;
@@ -1959,8 +2049,15 @@ interface MarketplaceViewProps {
1959
2049
  ui: UiStrings;
1960
2050
  /** Absent where there is no server to ask, so no dead button is drawn. */
1961
2051
  onRefresh?: () => void;
2052
+ /**
2053
+ * Writes a card asking Claude to install a plugin. Absent where the board
2054
+ * cannot be saved, so the button is not drawn where the card would be lost.
2055
+ */
2056
+ onRequestInstall?: (tool: MarketplaceTool, marketplace: Marketplace) => void;
2057
+ /** Whether an install card for this tool is already waiting on the board. */
2058
+ installRequested?: (toolId: string) => boolean;
1962
2059
  }
1963
- declare function MarketplaceView({ report, loading, failure, lang, ui, onRefresh }: MarketplaceViewProps): React.JSX.Element;
2060
+ declare function MarketplaceView({ report, loading, failure, lang, ui, onRefresh, onRequestInstall, installRequested, }: MarketplaceViewProps): React.JSX.Element;
1964
2061
 
1965
2062
  interface CardSheetProps {
1966
2063
  card: Card | null;
@@ -1973,4 +2070,4 @@ interface CardSheetProps {
1973
2070
  }
1974
2071
  declare function CardSheet({ card, state, lang, ui, onChange, onDelete, onClose }: CardSheetProps): React.JSX.Element | null;
1975
2072
 
1976
- export { ANALYSIS_SECTIONS, ARCHIVE_AFTER_DAYS, 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 GateReading, type GateStamp, type GateVerdict, 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_TEMPLATE_HASH, 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, SetupWizard, type SetupWizardLabels, type SetupWizardProps, type Severity, type SituationItem, SituationRow, type SituationRowProps, type SituationTone, 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, type WizardStep, applyMove, boardFacts, checkProgress, claimedWithoutStarting, claudeTheme, commandSnippet, countCards, cyberpunkTheme, decided, emptyCard, explainBoard, gateStatus, isArchived, isCard, isFirstRun, ledgerForm, midnightTheme, moveCardInColumns, newId, offeredThemes, oldMoneyTheme, panelForm, paneltirBuild, paperForm, readBoard, readStoredLang, repositorySnippet, resetGuide, sparkPoints, stampForColumn, storeLang, text, today, trendOf, undecided, untranslated, useDashboardForm, useDashboardTheme, useDelegatedClick, useGuideNote, useMediaQuery, validateBoard, warRoomForm, wizardStep, writeText };
2073
+ export { ANALYSIS_SECTIONS, ARCHIVE_AFTER_DAYS, 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 GateReading, type GateStamp, type GateVerdict, 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_TEMPLATE_HASH, 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 RequestKind, type Run, SetupGuide, type SetupGuideProps, type SetupRequirement, type SetupStatus, SetupWizard, type SetupWizardLabels, type SetupWizardProps, type Severity, type SituationItem, SituationRow, type SituationRowProps, type SituationTone, 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, type WizardStep, applyMove, boardFacts, checkProgress, claimedWithoutStarting, claudeTheme, commandSnippet, countCards, cyberpunkTheme, decided, dropIndex, emptyCard, explainBoard, gateStatus, isArchived, isCard, isFirstRun, ledgerForm, midnightTheme, moveCardInColumns, newId, offeredThemes, oldMoneyTheme, panelForm, paneltirBuild, paperForm, pendingRequest, queueColumn, readBoard, readStoredLang, repositorySnippet, requestId, resetGuide, sparkPoints, stampForColumn, storeLang, text, today, trendOf, undecided, untranslated, useDashboardForm, useDashboardTheme, useDelegatedClick, useGuideNote, useMediaQuery, validateBoard, warRoomForm, wizardStep, writeText };