paneltir 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,7 +30,10 @@ library shared with other projects. Rules:
30
30
  - The dashboard palette lives in <!-- path to this project's theme file, e.g.
31
31
  src/dashboard-theme.ts -->. Colour changes go there, never in the kit. The
32
32
  dashboard's **shape** is a `DashboardThemeForm` passed to the same provider
33
- (`panel` or `ledger`); it too is this project's choice, not the kit's.
33
+ (`panel`, `ledger` or `warRoom`); it too is this project's choice, not the
34
+ kit's. `warRoom` is the console one — square, tight, priority as a bar down
35
+ the left edge of each row — and the only one that fits more rows on a screen
36
+ rather than fewer.
34
37
  - **This project's theme goes to `PanelApp` as `extraThemes`, never as
35
38
  `themes`.** `extraThemes` offers it alongside the four the kit ships;
36
39
  `themes` means "exactly these and nothing else", so passing one theme there
@@ -52,6 +55,28 @@ library shared with other projects. Rules:
52
55
  moved underneath you, or both. The second is the one no `git diff` can find,
53
56
  because there is nothing in this project's history to diff against, and it is
54
57
  how a fix released upstream stayed out of a project for two releases.
58
+ - **A card finished more than a week ago leaves the board.** Nothing is
59
+ deleted and nothing is written: the panel derives it from `completedAt` on
60
+ every render, stops drawing that card in its column, and lists it in the
61
+ record view with the date it was finished. A done column that looks short has
62
+ not lost anything — read the board file, not the screen, when counting what
63
+ is finished. A card in a done column with no `completedAt` is never archived,
64
+ because its age is not known, which is one more reason to stamp the date when
65
+ a card is moved to done.
66
+ - **The top of the board says what is waiting on you and whether the system is
67
+ working.** Cards in the owner's column, questions nobody has answered, and
68
+ orders waiting on Claude, each as a count that filters the board or opens the
69
+ question when tapped — beside the health pills the project supplies. Nothing
70
+ to do there draws one sentence instead of a row of noughts.
71
+ - **A second problem found mid-card is fixed or filed, never a reason to
72
+ stop.** Small, reversible and inside what was already being touched: fixed in
73
+ the same pass. Anything larger becomes a card in `ideas` and the original work
74
+ carries on. And a detail a card does not specify is Claude's to choose — that
75
+ is the normal state of a card rather than a gap in it. Only `askme`, `explain`
76
+ and `hold` mean stop, and an undecided question holds back only the part of
77
+ the work that depends on it. A question that needs the owner is asked on the
78
+ board as a choice with `value: null`, where it outlives the conversation,
79
+ never as a session that stalls waiting for a reply.
55
80
  - **The panel explains itself once.** New readers get a short guided tour the
56
81
  first time they open the board; each note is dismissed and stays dismissed.
57
82
  **Help** in the header reports what the panel needs — the password, the
package/INSTALL.md CHANGED
@@ -19,7 +19,7 @@ identity** — never another project's.
19
19
  npm install paneltir
20
20
 
21
21
  # or, without npm access, straight from the repository:
22
- # npm install github:daifukus/paneltir#v0.11.0
22
+ # npm install github:daifukus/paneltir#v0.12.1
23
23
  ```
24
24
 
25
25
  (Use the tag you were given; never install without pinning a version.)
@@ -237,7 +237,7 @@ identity** — never another project's.
237
237
 
238
238
  ```bash
239
239
  npx paneltir version # version, fingerprint, where it came from
240
- npx paneltir check 0.11.0 # exits non-zero if that is not what is installed
240
+ npx paneltir check 0.12.1 # exits non-zero if that is not what is installed
241
241
  npx paneltir board # read the board and say what is wrong with it
242
242
  ```
243
243
 
package/README.md CHANGED
@@ -54,7 +54,7 @@ tag rather than a branch, so an unfinished push cannot reach a project:
54
54
  npm install paneltir
55
55
 
56
56
  # or, without npm access, straight from the repository:
57
- # npm install github:daifukus/paneltir#v0.11.0
57
+ # npm install github:daifukus/paneltir#v0.12.1
58
58
  ```
59
59
 
60
60
  Installing from Git runs the `prepare` script, which builds `dist/`, so the
package/bin/paneltir.mjs CHANGED
@@ -614,8 +614,25 @@ async function run(args) {
614
614
  // rendered at the bottom of the revisions log and fell outside the bell's
615
615
  // newest-first window — a record of work that read as the first thing ever
616
616
  // done here.
617
+ /*
618
+ * An id the board does not already have. The id used to be `run-<date>-<n>`
619
+ * with `n` the list's length, which is unique only while the list only ever
620
+ * grows: once it reaches the cap below and is trimmed, the length stops
621
+ * moving and every pass on the same day gets the same id. Two runs sharing
622
+ * an id is two React keys colliding in the revisions log, and one bell entry
623
+ * standing for both — mark the first seen and the second is silenced too,
624
+ * which is the fault `PanelNotification.id` warns about by name.
625
+ */
626
+ const used = new Set(board.runs.map((run) => run?.id))
627
+ let serial = board.runs.length + 1
628
+ let id = `run-${date}-${String(serial).padStart(3, '0')}`
629
+ while (used.has(id)) {
630
+ serial += 1
631
+ id = `run-${date}-${String(serial).padStart(3, '0')}`
632
+ }
633
+
617
634
  board.runs.push({
618
- id: `run-${date}-${String(board.runs.length + 1).padStart(3, '0')}`,
635
+ id,
619
636
  date,
620
637
  by,
621
638
  summary,
package/dist/index.d.ts CHANGED
@@ -104,8 +104,13 @@ interface DashboardThemeForm {
104
104
  * carrying a coloured bar. The default.
105
105
  * - `ledger` — cards are ruled rows in a column with no fill, separated by
106
106
  * hairlines and marked in the gutter. Reads as a book rather than a HUD.
107
+ * - `warRoom` — an operations console. Square, hairline-ruled, tight, with
108
+ * priority carried as a full-height bar at the left edge of every row and
109
+ * counts set in the monospace face so columns of numbers line up. It is
110
+ * the shape for a board somebody is watching rather than browsing: state
111
+ * before ornament, and as many rows on the screen as will fit.
107
112
  */
108
- shape?: 'panel' | 'ledger';
113
+ shape?: 'panel' | 'ledger' | 'warRoom';
109
114
  /** Radius of columns, tiles and sheets. */
110
115
  radiusPanel?: string;
111
116
  /** Radius of cards. */
@@ -195,10 +200,25 @@ declare const ledgerForm: DashboardThemeForm;
195
200
  * free, so the shape does the separating instead.
196
201
  */
197
202
  declare const paperForm: DashboardThemeForm;
203
+ /**
204
+ * An operations console: square, tight, ruled, state before ornament.
205
+ *
206
+ * `panel` is a board you browse and `ledger` is a book you read; this is one
207
+ * somebody is watching. So the spacing scale comes down rather than up — it is
208
+ * the only form below 1 — the radii go flat, the labels are tracked out the way
209
+ * a console tracks them, and the face for anything countable is the monospace
210
+ * one, because a column of numbers that does not line up cannot be scanned.
211
+ *
212
+ * It changes the drawing and not the palette: a war room in Old Money's greens
213
+ * is still Old Money. That is the whole point of a form being separate from
214
+ * its colour.
215
+ */
216
+ declare const warRoomForm: DashboardThemeForm;
198
217
  declare const THEME_FORMS: {
199
218
  readonly panel: DashboardThemeForm;
200
219
  readonly ledger: DashboardThemeForm;
201
220
  readonly paper: DashboardThemeForm;
221
+ readonly warRoom: DashboardThemeForm;
202
222
  };
203
223
  type ThemeFormName = keyof typeof THEME_FORMS;
204
224
  declare const THEME_PRESETS: {
@@ -257,6 +277,63 @@ declare function HealthPillRow({ children }: {
257
277
  children: React.ReactNode;
258
278
  }): React.JSX.Element;
259
279
 
280
+ /**
281
+ * How loudly an item asks to be looked at.
282
+ *
283
+ * Deliberately three rather than the four a health pill carries: this row
284
+ * answers "is anything waiting on me", and "unknown" is not an answer to that
285
+ * question — a number is either zero or it is not.
286
+ */
287
+ type SituationTone = 'clear' | 'attention' | 'alarm';
288
+ interface SituationItem {
289
+ id: string;
290
+ /** What is waiting. Short: this is read at a glance, not studied. */
291
+ label: string;
292
+ count: number;
293
+ /** Who it is waiting on, or what it means. One line, under the label. */
294
+ hint?: string;
295
+ tone?: SituationTone;
296
+ /** Where looking at it leads. Without this the number is not a control. */
297
+ onClick?: () => void;
298
+ }
299
+ interface SituationRowProps {
300
+ /** The heading over the left half. */
301
+ blockingLabel: string;
302
+ /**
303
+ * What is outstanding. An item with a count of zero is still drawn — a
304
+ * counter that disappears when it reaches zero cannot be read as "nothing is
305
+ * waiting", only as "the panel is not telling me", which is the reading this
306
+ * row exists to prevent.
307
+ */
308
+ blocking: SituationItem[];
309
+ /** Said when every count is zero, in place of a row of noughts. */
310
+ clearLabel: string;
311
+ /** The heading over the right half. */
312
+ statusLabel: string;
313
+ /** The system's own state — health pills, a build, a version. */
314
+ status?: React.ReactNode;
315
+ }
316
+ /**
317
+ * The two things a board is opened to find out: whether anything is waiting on
318
+ * the reader, and whether the machinery under it is working.
319
+ *
320
+ * Both were already on the screen and neither was prominent. The counts sat in
321
+ * a grid of six identical tiles, where "three cards are yours" reads exactly as
322
+ * loudly as "forty-one are done"; the health pills sat under them as a line of
323
+ * small grey text. A board is looked at to decide what to do next, and a panel
324
+ * that makes that answer the same size as everything else has made the reader
325
+ * do the finding.
326
+ *
327
+ * So the outstanding counts are drawn large and are controls rather than
328
+ * labels, the whole left half goes quiet when there is nothing outstanding, and
329
+ * the system's state gets its own half with a heading instead of being a footer
330
+ * under the numbers.
331
+ *
332
+ * It draws no colour of its own: tone becomes a class and the palette arrives
333
+ * through `--pt-color-*`, like everything else here.
334
+ */
335
+ declare function SituationRow({ blockingLabel, blocking, clearLabel, statusLabel, status, }: SituationRowProps): React.JSX.Element;
336
+
260
337
  interface FilterChipProps {
261
338
  label: string;
262
339
  count?: number;
@@ -285,11 +362,15 @@ interface BoardColumnData<TCard extends BoardCardData = BoardCardData> {
285
362
  hint?: string;
286
363
  cards: TCard[];
287
364
  /**
288
- * Drawn under the cards, inside the column's own scroll.
365
+ * Drawn under the cards, inside the column's own scroll, so it takes no
366
+ * height from them.
289
367
  *
290
- * It exists for what a column has to say about cards it is *not* showing —
291
- * a done column hiding what was finished last month still owes the reader a
292
- * line saying so, or the cards have not been hidden, they have been lost.
368
+ * It was added for a done column to account for what it was no longer
369
+ * drawing, and the panel no longer uses it that way: a count in the middle of
370
+ * the board is a control for putting the cards back in the way, and the
371
+ * archive is now a place you go to instead. The slot stays because it is the
372
+ * honest shape for anything a column has to say about itself — a caller with
373
+ * such a line has somewhere to put it.
293
374
  */
294
375
  footer?: React.ReactNode;
295
376
  }
@@ -297,6 +378,12 @@ interface BoardMoveResult {
297
378
  cardId: string;
298
379
  fromColumnId: string;
299
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
+ */
300
387
  toIndex: number;
301
388
  }
302
389
 
@@ -342,6 +429,28 @@ declare function Card$1({ cardId, columnId, index, title, priorityColor, tags, p
342
429
  */
343
430
  declare function moveCardInColumns<TCard extends BoardCardData>(columns: BoardColumnData<TCard>[], move: BoardMoveResult): BoardColumnData<TCard>[];
344
431
 
432
+ interface DropTarget {
433
+ columnId: string;
434
+ index: number;
435
+ }
436
+ /**
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.
451
+ */
452
+ declare function dropIndex(source: DropTarget, target: DropTarget): number | null;
453
+
345
454
  interface DetailSheetProps {
346
455
  open: boolean;
347
456
  onClose: () => void;
@@ -1095,6 +1204,23 @@ declare function isArchived(card: Card, doneColumns: ReadonlySet<string>, now?:
1095
1204
  * finished, and one dragged back out of done is not finished any more.
1096
1205
  */
1097
1206
  declare function stampForColumn(card: Card, columns: ColumnDef[]): Card;
1207
+ /**
1208
+ * Where a card the panel writes on the reader's behalf goes.
1209
+ *
1210
+ * The update card and the four "ask for this again" cards used to be born in
1211
+ * a column literally called `next`. The default board has one; a board whose
1212
+ * owner renamed it does not, and a card in a column that does not exist is
1213
+ * the exact fault `validateBoard` names — drawn nowhere, and refused by the
1214
+ * write path, so one tap on a button turned a saveable board into one that
1215
+ * could not be saved and said so about a column the reader had never heard of.
1216
+ *
1217
+ * `next` when there is one, because that is what the button means. Otherwise
1218
+ * the first column that is neither in flight nor finished nor the owner's:
1219
+ * queued work goes where queued work already sits. And the first column of
1220
+ * all when nothing else fits, since a board with no columns cannot pass the
1221
+ * validator either way.
1222
+ */
1223
+ declare function queueColumn(columns: ColumnDef[]): string;
1098
1224
  /** A card claiming to be finished that was never picked up. */
1099
1225
  declare function claimedWithoutStarting(card: Card): boolean;
1100
1226
  interface Counts {
@@ -1384,12 +1510,19 @@ interface UiStrings {
1384
1510
  filterForClaude: string;
1385
1511
  filterYours: string;
1386
1512
  filterHighRisk: string;
1513
+ situationBlocking: string;
1514
+ situationClear: string;
1515
+ situationStatus: string;
1516
+ situationYoursHint: string;
1517
+ situationDecisionsHint: string;
1518
+ situationOrdersHint: string;
1387
1519
  statOpen: string;
1388
1520
  statOrders: string;
1389
1521
  statYours: string;
1390
1522
  statDecisions: string;
1391
1523
  statHighRisk: string;
1392
1524
  statDone: string;
1525
+ statArchived: string;
1393
1526
  theme: string;
1394
1527
  themeSubtitle: string;
1395
1528
  themeNote: string;
@@ -1420,9 +1553,10 @@ interface UiStrings {
1420
1553
  needTokenEnables: string;
1421
1554
  needRepo: string;
1422
1555
  needRepoEnables: string;
1423
- archivedOne: string;
1424
- archivedMany: string;
1425
- archivedHide: string;
1556
+ archiveTitle: string;
1557
+ archiveHeading: string;
1558
+ archiveIntro: string;
1559
+ archiveFinished: (date: string) => string;
1426
1560
  needGate: string;
1427
1561
  needGateEnables: string;
1428
1562
  fixGate: string;
@@ -1884,4 +2018,4 @@ interface CardSheetProps {
1884
2018
  }
1885
2019
  declare function CardSheet({ card, state, lang, ui, onChange, onDelete, onClose }: CardSheetProps): React.JSX.Element | null;
1886
2020
 
1887
- 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, 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, wizardStep, writeText };
2021
+ 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, dropIndex, emptyCard, explainBoard, gateStatus, isArchived, isCard, isFirstRun, ledgerForm, midnightTheme, moveCardInColumns, newId, offeredThemes, oldMoneyTheme, panelForm, paneltirBuild, paperForm, queueColumn, readBoard, readStoredLang, repositorySnippet, resetGuide, sparkPoints, stampForColumn, storeLang, text, today, trendOf, undecided, untranslated, useDashboardForm, useDashboardTheme, useDelegatedClick, useGuideNote, useMediaQuery, validateBoard, warRoomForm, wizardStep, writeText };