sign-off-axi 0.5.6 → 0.5.8

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/cli.js CHANGED
@@ -13,7 +13,7 @@ import { parseArgs } from 'node:util';
13
13
  import { encode } from '@toon-format/toon';
14
14
  import { acknowledgeOwner, checkOwner, deliveries, dispatchOwners, registerOwner } from './owner.js';
15
15
  import { AxiError, runAxiCli } from 'axi-sdk-js';
16
- import { ATTACH_DEFAULT_MB, ATTACH_MAX, ATTACH_NAME, CHAPTERS_MAX, SIGNED_IN, CLEARED, InputError, MEDIA_MAX, NOTE_MAX, REPLY, answersPath, appendAnnotation, appendPartComment, annotationsPath, markCommentsRead, readPartComments, replyComment, asks, appendAnswer, appendDismissed, appendSendBack, appendDesignAnswer, appendMessage, appendRound, awaitsVerdict, lapsed, askEngagement, checkEngagement, markEngagementSeen, appendRead, answerDeeper, askDeeper, deeperPath, guidePart, markDeeperRead, readDeeper, readReads, seenAt, isKey, loadDecisions, markDelivered, markSeen, messagesPath, readAnnotations, readAnswers, reading, readDismissed, readItems, sameDecision, readMessages, readRounds, roundsPath, seenBy, seenPath, sentIds, unreadable as reasonOf, writeItems } from './store.js';
16
+ import { ATTACH_DEFAULT_MB, ATTACH_MAX, ATTACH_NAME, CHAPTERS_MAX, SIGNED_IN, CLEARED, InputError, MEDIA_MAX, NOTE_MAX, REPLY, answersPath, appendAnnotation, appendPartComment, annotationsPath, markCommentsRead, readPartComments, replyComment, asks, appendAnswer, appendDismissed, appendSendBack, appendDesignAnswer, appendMessage, appendRound, awaitsVerdict, lapsed, askEngagement, checkEngagement, markEngagementSeen, appendRead, appendOpened, readOpened, answerDeeper, askDeeper, deeperPath, guidePart, markDeeperRead, readDeeper, readReads, seenAt, isKey, loadDecisions, markDelivered, markSeen, messagesPath, readAnnotations, readAnswers, reading, readDismissed, readItems, sameDecision, readMessages, readRounds, roundsPath, seenBy, seenPath, sentIds, unreadable as reasonOf, writeItems } from './store.js';
17
17
  import { ContractError, KINDS, MEDIA_BYTES, REPORT_INIT, checkMessage, diagramPages, diagramViews, guided, mediaFiles, parseMessage, reportPage } from './contract.js';
18
18
  import { faults, isStill } from './evidence-text.js';
19
19
  import { deliverVault, errors, readVaultExpiry, sweepVault, takeVaultExpiries, VaultError } from './vault.js';
@@ -2655,6 +2655,7 @@ const state = () => {
2655
2655
  seen: Object.values(answers).filter(taken).map((a) => a.key),
2656
2656
  seen_at: Object.fromEntries(Object.values(answers).flatMap((a) => { const t = seenAt(file)(a); return t ? [[a.key, t]] : []; })),
2657
2657
  reads: readReads(file),
2658
+ opened: readOpened(file),
2658
2659
  deeper: readDeeper(file),
2659
2660
  delivery: deliveries(file),
2660
2661
  rounds,
@@ -2986,6 +2987,14 @@ async function route(req) {
2986
2987
  askEngagement(file, input);
2987
2988
  return [200, state()];
2988
2989
  }
2990
+ if (req.url === '/api/reads' && Array.isArray(JSON.parse(body)?.keys)) {
2991
+ // A whole report opened, or marked read or unread from Reports: keys and read, no part.
2992
+ const { keys, read } = JSON.parse(body), have = new Set(board(file).map((x) => x.key));
2993
+ if (!keys.length || keys.length > 200 || typeof read !== 'boolean' || !keys.every((k) => typeof k === 'string' && have.has(k)))
2994
+ return [400, { error: 'Send 1 to 200 keys on the board and read true or false' }];
2995
+ appendOpened(file, [...new Set(keys)], read);
2996
+ return [200, state()];
2997
+ }
2989
2998
  if (req.url === '/api/reads' || req.url === '/api/deeper') {
2990
2999
  const { key, part, q } = JSON.parse(body) ?? {}, d = loadDecisions(file).find((x) => x.key === key), about = typeof part === 'string' && d ? guidePart(d, part) : undefined;
2991
3000
  if (!about)
package/dist/store.js CHANGED
@@ -595,6 +595,20 @@ export const appendRead = (decisionsPath, key, part) => {
595
595
  if (readReads(decisionsPath)[`${key}/${part}`] === undefined)
596
596
  appendLine(readsPath(decisionsPath), { key, part, by: 'captain', at: now() });
597
597
  };
598
+ /** Whole reports opened or marked unread, in the same file with no part: the last line for a key wins, so Reports' read and unread toggle (true read, false unread). */
599
+ export function readOpened(decisionsPath) {
600
+ const last = {};
601
+ readRaw(readsPath(decisionsPath)).split('\n').forEach((line) => {
602
+ try {
603
+ const r = line && JSON.parse(line);
604
+ if (r && typeof r.key === 'string' && r.part === undefined && typeof r.read === 'boolean')
605
+ last[r.key] = r.read;
606
+ }
607
+ catch { }
608
+ });
609
+ return last;
610
+ }
611
+ export const appendOpened = (decisionsPath, keys, read) => keys.forEach((key) => appendLine(readsPath(decisionsPath), { key, read, by: 'captain', at: now() }));
598
612
  export const deeperPath = (decisionsPath) => decisionsPath.replace(/\.json$/, '') + '.deeper.jsonl';
599
613
  export function readDeeper(decisionsPath) {
600
614
  const all = new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sign-off-axi",
3
- "version": "0.5.6",
3
+ "version": "0.5.8",
4
4
  "description": "One review board for evidence, quick yes/no decisions and retro changes.",
5
5
  "type": "module",
6
6
  "bin": { "sign-off-axi": "dist/cli.js" },