@north-light/crouter 0.3.194 → 0.3.196

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.
@@ -18,8 +18,8 @@
18
18
  */
19
19
  import { registerElement } from '../register.js';
20
20
  import { readSlotConfig, readSlotId } from '../slot-config.js';
21
- import { artifactData, respond } from '../host.js';
22
- import { anchorLabel, findComment, setComment } from '../comments.js';
21
+ import { artifactData, respond, savedIds, savedResponse } from '../host.js';
22
+ import { adoptComments, anchorLabel, findComment, setComment } from '../comments.js';
23
23
  const COMMENT_BUBBLE = 'M3.25 3.5h9.5A1.75 1.75 0 0 1 14.5 5.25v5A1.75 1.75 0 0 1 12.75 12H7.4l-3.15 2.36A.5.5 0 0 1 3.5 14v-2H3.25A1.75 1.75 0 0 1 1.5 10.25v-5A1.75 1.75 0 0 1 3.25 3.5Z';
24
24
  const STYLE = `
25
25
  :host {
@@ -421,6 +421,7 @@ class CrtrTableElement extends HTMLElement {
421
421
  return;
422
422
  }
423
423
  this.#rows = config.rows;
424
+ this.#hydrate();
424
425
  this.#renderTable();
425
426
  }
426
427
  disconnectedCallback() {
@@ -440,8 +441,36 @@ class CrtrTableElement extends HTMLElement {
440
441
  return;
441
442
  }
442
443
  this.#rows = rows.rows;
444
+ this.#hydrate();
443
445
  this.#renderTable();
444
446
  }
447
+ /**
448
+ * Seed local state from the answer the host is already holding, so a reopened page shows
449
+ * the rows and columns that were picked. It runs once the rows are known — after a `source`
450
+ * load, not before — because a saved id is only honoured while it still names something on
451
+ * screen. The select modes are re-imposed for the same reason.
452
+ */
453
+ #hydrate() {
454
+ if (this.#slotId === undefined)
455
+ return;
456
+ const saved = savedResponse(this.#slotId);
457
+ if (saved === undefined)
458
+ return;
459
+ const rows = this.#orderRows(savedIds(saved.selectedRowIds));
460
+ const columns = this.#orderColumns(savedIds(saved.selectedColumnIds));
461
+ this.#selectedRowIds = this.#rowSelect === 'none' ? [] : this.#rowSelect === 'single' ? rows.slice(0, 1) : rows;
462
+ this.#selectedColumnIds =
463
+ this.#columnSelect === 'none' ? [] : this.#columnSelect === 'single' ? columns.slice(0, 1) : columns;
464
+ const rowIds = new Set(this.#rows.map((row) => row.id));
465
+ const columnIds = new Set((this.#config?.columns ?? []).map((column) => column.id));
466
+ this.#comments = adoptComments(saved.comments).filter((comment) => {
467
+ if (comment.anchor.kind === 'row')
468
+ return rowIds.has(comment.anchor.rowId);
469
+ if (comment.anchor.kind === 'column')
470
+ return columnIds.has(comment.anchor.columnId);
471
+ return true;
472
+ });
473
+ }
445
474
  // ── the response ───────────────────────────────────────────────────────────────────────
446
475
  get #rowSelect() {
447
476
  return this.#config?.rowSelect ?? 'none';
@@ -24,8 +24,8 @@
24
24
  */
25
25
  import { registerElement } from '../register.js';
26
26
  import { readSlotConfig, readSlotId } from '../slot-config.js';
27
- import { respond } from '../host.js';
28
- import { findComment, setComment } from '../comments.js';
27
+ import { respond, savedResponse } from '../host.js';
28
+ import { adoptComments, findComment, setComment } from '../comments.js';
29
29
  const WHOLE = { kind: 'whole' };
30
30
  const WHOLE_TARGET = 'the whole passage';
31
31
  const RANGE_TARGET = 'the selected text';
@@ -622,6 +622,7 @@ class CrtrTextElement extends HTMLElement {
622
622
  this.#config = read.config;
623
623
  this.#slotId = readSlotId(this);
624
624
  this.#text = read.config.initialText;
625
+ this.#hydrate();
625
626
  this.#editing = read.config.editable === true && this.#text.length === 0;
626
627
  this.#renderToolbar();
627
628
  this.#renderSurface();
@@ -630,6 +631,22 @@ class CrtrTextElement extends HTMLElement {
630
631
  disconnectedCallback() {
631
632
  document.removeEventListener('pointerdown', this.#onDocumentPointerDown, true);
632
633
  }
634
+ /**
635
+ * Seed local state from the answer the host is already holding, so a reopened page shows
636
+ * the edited words and the comments on them rather than the agent's original text. An
637
+ * uneditable slot keeps its authored text — only comments carry over — because a saved
638
+ * edit there could not have come from this control.
639
+ */
640
+ #hydrate() {
641
+ if (this.#slotId === undefined)
642
+ return;
643
+ const saved = savedResponse(this.#slotId);
644
+ if (saved === undefined)
645
+ return;
646
+ if (this.#config.editable === true && typeof saved.text === 'string')
647
+ this.#text = saved.text;
648
+ this.#comments = adoptComments(saved.comments);
649
+ }
633
650
  /** The complete current response — what every change hands `respond()`. */
634
651
  #response() {
635
652
  return { text: this.#text, edited: this.#text !== this.#config.initialText, comments: this.#comments };
@@ -16,6 +16,12 @@
16
16
  * the host autosave the partial map; it does not resolve the ticket. Only the pager's
17
17
  * submit affordance calls `submit()`, which posts the complete map once.
18
18
  *
19
+ * `respond()` has one counterpart, `savedResponse()`: the host hands back the response it is
20
+ * currently holding for a slot, so a page reopened after a partial answer re-mounts SHOWING
21
+ * that answer instead of blank. Every response-bearing element reads it at mount and seeds
22
+ * its visible state from it — that is what keeps "what submit sends" and "what the screen
23
+ * shows" the same thing.
24
+ *
19
25
  * The host is optional at the type level (`window.crtr?`) because a page can be opened
20
26
  * bare — dropped in a browser, or rendered by a host that installs nothing. Elements read
21
27
  * the bridge through the accessors below rather than touching `window.crtr` directly, so
@@ -42,6 +48,12 @@ export interface CrtrHost {
42
48
  * partial map. Called on every user change. NOT ticket resolution.
43
49
  */
44
50
  respond(slotId: string, response: SlotResponse): void;
51
+ /**
52
+ * The response the host currently holds for this slot — an autosaved partial answer on a
53
+ * reopened page, or the published answer on a resolved one. `undefined` when the host has
54
+ * nothing for the slot. Elements read it at mount; it is the inbound half of `respond`.
55
+ */
56
+ savedResponse(slotId: string): SlotResponse | undefined;
45
57
  /** Posts the complete response map once. Only the pager's submit affordance calls it. */
46
58
  submit(): Promise<void>;
47
59
  /** Flushes the pending autosave of the partial map. */
@@ -98,6 +110,20 @@ export declare function host(): CrtrHost | undefined;
98
110
  * so an unavailable host only means the answer is not being persisted.
99
111
  */
100
112
  export declare function respond(slotId: string, response: SlotResponse): HostCall;
113
+ /**
114
+ * The response the host already holds for this slot, or `undefined` when it holds none — a
115
+ * bare page, a first open, or a host without the channel. Elements call this once at mount
116
+ * and seed their visible state from it, so a reopened page shows the answers the host would
117
+ * submit rather than an empty form beside a full response map.
118
+ *
119
+ * `T` is asserted, not verified, exactly like `readSlotConfig`'s config: the host's saved map
120
+ * was validated against this slot's kind before it was stored. What is not guaranteed is that
121
+ * a host implements the channel at all, which is what the `undefined` covers — so seed with
122
+ * the same defensiveness you would give any outside value (`savedIds`, `adoptComments`).
123
+ */
124
+ export declare function savedResponse<T extends SlotResponse = SlotResponse>(slotId: string): T | undefined;
125
+ /** The string ids of a saved selection field, with anything else in it dropped. */
126
+ export declare function savedIds(value: unknown): string[];
101
127
  /**
102
128
  * Posts the complete response map once, resolving the ticket. Only the pager's submit
103
129
  * affordance calls this.
@@ -16,6 +16,12 @@
16
16
  * the host autosave the partial map; it does not resolve the ticket. Only the pager's
17
17
  * submit affordance calls `submit()`, which posts the complete map once.
18
18
  *
19
+ * `respond()` has one counterpart, `savedResponse()`: the host hands back the response it is
20
+ * currently holding for a slot, so a page reopened after a partial answer re-mounts SHOWING
21
+ * that answer instead of blank. Every response-bearing element reads it at mount and seeds
22
+ * its visible state from it — that is what keeps "what submit sends" and "what the screen
23
+ * shows" the same thing.
24
+ *
19
25
  * The host is optional at the type level (`window.crtr?`) because a page can be opened
20
26
  * bare — dropped in a browser, or rendered by a host that installs nothing. Elements read
21
27
  * the bridge through the accessors below rather than touching `window.crtr` directly, so
@@ -52,6 +58,33 @@ export function respond(slotId, response) {
52
58
  return { status: 'failed', reason: reasonOf(error) };
53
59
  }
54
60
  }
61
+ /**
62
+ * The response the host already holds for this slot, or `undefined` when it holds none — a
63
+ * bare page, a first open, or a host without the channel. Elements call this once at mount
64
+ * and seed their visible state from it, so a reopened page shows the answers the host would
65
+ * submit rather than an empty form beside a full response map.
66
+ *
67
+ * `T` is asserted, not verified, exactly like `readSlotConfig`'s config: the host's saved map
68
+ * was validated against this slot's kind before it was stored. What is not guaranteed is that
69
+ * a host implements the channel at all, which is what the `undefined` covers — so seed with
70
+ * the same defensiveness you would give any outside value (`savedIds`, `adoptComments`).
71
+ */
72
+ export function savedResponse(slotId) {
73
+ const bridge = host();
74
+ if (typeof bridge?.savedResponse !== 'function')
75
+ return undefined;
76
+ try {
77
+ const saved = bridge.savedResponse(slotId);
78
+ return typeof saved === 'object' && saved !== null && !Array.isArray(saved) ? saved : undefined;
79
+ }
80
+ catch {
81
+ return undefined;
82
+ }
83
+ }
84
+ /** The string ids of a saved selection field, with anything else in it dropped. */
85
+ export function savedIds(value) {
86
+ return Array.isArray(value) ? value.filter((id) => typeof id === 'string') : [];
87
+ }
55
88
  /**
56
89
  * Posts the complete response map once, resolving the ticket. Only the pager's submit
57
90
  * affordance calls this.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/crouter",
3
- "version": "0.3.194",
3
+ "version": "0.3.196",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/runtime.lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@north-light/crouter",
3
- "version": "0.3.194",
3
+ "version": "0.3.196",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/crouter",
9
- "version": "0.3.194",
9
+ "version": "0.3.196",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "dependencies": {