@graview/primitives 0.1.65 β†’ 0.1.67

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.
Files changed (136) hide show
  1. package/dist/app-bar.d.ts +114 -15
  2. package/dist/app-bar.d.ts.map +1 -1
  3. package/dist/app-bar.js +192 -130
  4. package/dist/app-bar.js.map +1 -1
  5. package/dist/app-menu.d.ts +9 -0
  6. package/dist/app-menu.d.ts.map +1 -0
  7. package/dist/app-menu.js +60 -0
  8. package/dist/app-menu.js.map +1 -0
  9. package/dist/bar-activity.d.ts +5 -9
  10. package/dist/bar-activity.d.ts.map +1 -1
  11. package/dist/bar-activity.js +24 -7
  12. package/dist/bar-activity.js.map +1 -1
  13. package/dist/bar-panes.d.ts +5 -5
  14. package/dist/bar-panes.d.ts.map +1 -1
  15. package/dist/bar-panes.js +9 -5
  16. package/dist/bar-panes.js.map +1 -1
  17. package/dist/bar-suggested.d.ts +9 -1
  18. package/dist/bar-suggested.d.ts.map +1 -1
  19. package/dist/bar-suggested.js +11 -17
  20. package/dist/bar-suggested.js.map +1 -1
  21. package/dist/better-bar.d.ts +77 -0
  22. package/dist/better-bar.d.ts.map +1 -0
  23. package/dist/better-bar.js +312 -0
  24. package/dist/better-bar.js.map +1 -0
  25. package/dist/better-panel.d.ts +55 -0
  26. package/dist/better-panel.d.ts.map +1 -0
  27. package/dist/better-panel.js +151 -0
  28. package/dist/better-panel.js.map +1 -0
  29. package/dist/editable.d.ts +8 -1
  30. package/dist/editable.d.ts.map +1 -1
  31. package/dist/editable.js +140 -16
  32. package/dist/editable.js.map +1 -1
  33. package/dist/first-look-door.d.ts +2 -2
  34. package/dist/first-look-door.js +2 -2
  35. package/dist/first-look.d.ts.map +1 -1
  36. package/dist/first-look.js +9 -7
  37. package/dist/first-look.js.map +1 -1
  38. package/dist/frame.d.ts +2 -2
  39. package/dist/frame.d.ts.map +1 -1
  40. package/dist/frame.js +1 -1
  41. package/dist/frame.js.map +1 -1
  42. package/dist/inbox-pane.d.ts +31 -0
  43. package/dist/inbox-pane.d.ts.map +1 -0
  44. package/dist/inbox-pane.js +89 -0
  45. package/dist/inbox-pane.js.map +1 -0
  46. package/dist/inbox.d.ts +17 -0
  47. package/dist/inbox.d.ts.map +1 -0
  48. package/dist/inbox.js +84 -0
  49. package/dist/inbox.js.map +1 -0
  50. package/dist/index.d.ts +4 -3
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +3 -2
  53. package/dist/index.js.map +1 -1
  54. package/dist/lens/calendar-drawing.js +7 -3
  55. package/dist/lens/calendar-drawing.js.map +1 -1
  56. package/dist/menu-suggested.d.ts +54 -0
  57. package/dist/menu-suggested.d.ts.map +1 -0
  58. package/dist/menu-suggested.js +140 -0
  59. package/dist/menu-suggested.js.map +1 -0
  60. package/dist/menu-told-door.js +1 -1
  61. package/dist/menu-told-door.js.map +1 -1
  62. package/dist/menu-told.d.ts +7 -0
  63. package/dist/menu-told.d.ts.map +1 -1
  64. package/dist/menu-told.js +24 -0
  65. package/dist/menu-told.js.map +1 -1
  66. package/dist/notify-sheet.js +2 -2
  67. package/dist/notify-sheet.js.map +1 -1
  68. package/dist/page-better.d.ts +27 -0
  69. package/dist/page-better.d.ts.map +1 -0
  70. package/dist/page-better.js +747 -0
  71. package/dist/page-better.js.map +1 -0
  72. package/dist/page-parts.d.ts +41 -0
  73. package/dist/page-parts.d.ts.map +1 -0
  74. package/dist/page-parts.js +101 -0
  75. package/dist/page-parts.js.map +1 -0
  76. package/dist/pages.d.ts +2 -3
  77. package/dist/pages.d.ts.map +1 -1
  78. package/dist/pages.js +2 -3
  79. package/dist/pages.js.map +1 -1
  80. package/dist/profile.d.ts +1 -10
  81. package/dist/profile.d.ts.map +1 -1
  82. package/dist/profile.js +2 -3
  83. package/dist/profile.js.map +1 -1
  84. package/dist/scene-bar.d.ts +1 -1
  85. package/dist/scene-bar.d.ts.map +1 -1
  86. package/dist/scene-bar.js +4 -7
  87. package/dist/scene-bar.js.map +1 -1
  88. package/dist/section-better.d.ts +66 -9
  89. package/dist/section-better.d.ts.map +1 -1
  90. package/dist/section-better.js +285 -136
  91. package/dist/section-better.js.map +1 -1
  92. package/dist/section-door.d.ts +7 -0
  93. package/dist/section-door.d.ts.map +1 -1
  94. package/dist/section-door.js +39 -5
  95. package/dist/section-door.js.map +1 -1
  96. package/dist/section-menu.d.ts +23 -9
  97. package/dist/section-menu.d.ts.map +1 -1
  98. package/dist/section-menu.js +176 -23
  99. package/dist/section-menu.js.map +1 -1
  100. package/dist/section-words.d.ts +8 -0
  101. package/dist/section-words.d.ts.map +1 -1
  102. package/dist/section-words.js +25 -0
  103. package/dist/section-words.js.map +1 -1
  104. package/dist/shell.d.ts +7 -1
  105. package/dist/shell.d.ts.map +1 -1
  106. package/dist/shell.js +4 -2
  107. package/dist/shell.js.map +1 -1
  108. package/dist/suggested.d.ts +21 -10
  109. package/dist/suggested.d.ts.map +1 -1
  110. package/dist/suggested.js +80 -66
  111. package/dist/suggested.js.map +1 -1
  112. package/dist/suggestion-sentence.d.ts +50 -0
  113. package/dist/suggestion-sentence.d.ts.map +1 -0
  114. package/dist/suggestion-sentence.js +119 -0
  115. package/dist/suggestion-sentence.js.map +1 -0
  116. package/dist/told-card.js +1 -1
  117. package/dist/told-card.js.map +1 -1
  118. package/dist/told-ui.d.ts +1 -1
  119. package/dist/told-ui.d.ts.map +1 -1
  120. package/dist/told-ui.js +1 -1
  121. package/dist/told-ui.js.map +1 -1
  122. package/dist/when-words.d.ts +21 -0
  123. package/dist/when-words.d.ts.map +1 -0
  124. package/dist/when-words.js +43 -0
  125. package/dist/when-words.js.map +1 -0
  126. package/dist/workbench/activity.d.ts +44 -0
  127. package/dist/workbench/activity.d.ts.map +1 -1
  128. package/dist/workbench/activity.js +74 -59
  129. package/dist/workbench/activity.js.map +1 -1
  130. package/dist/workbench/inspector.d.ts.map +1 -1
  131. package/dist/workbench/inspector.js +170 -31
  132. package/dist/workbench/inspector.js.map +1 -1
  133. package/dist/workbench/standing.d.ts.map +1 -1
  134. package/dist/workbench/standing.js +1 -1
  135. package/dist/workbench/standing.js.map +1 -1
  136. package/package.json +6 -6
@@ -1,17 +1,18 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { layer } from "@graview/core";
3
- import { compareViewRevision } from "@graview/core/revisions";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { labelOf, layer } from "@graview/core";
3
+ import { compareViewRevision, sectionsOfDrawing } from "@graview/core/revisions";
4
4
  import { useGraview } from "@graview/react/provider";
5
5
  import { sectionUnderNow, viewPlaceOf } from "@graview/react/drawing";
6
- import { reviseSection } from "@graview/tools/section";
6
+ import { CONTROL_WORDS, describeChange, drawnFindings, lookOf, lostUnlessReworded, runDesignAgent } from "@graview/tools/section";
7
7
  import { interactionId } from "@graview/tools/inline-ai";
8
8
  import { retryingImport } from "@graview/core/retry";
9
9
  import { lazyModule } from "@graview/react/provider";
10
10
  import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
11
11
  import { boxOf, SectionOutline } from "./section-outline.js";
12
+ import { headingsOf, readDrawn, recordLabelsOf, Redesigning } from "./better-bar.js";
13
+ import { BetterPanel } from "./better-panel.js";
12
14
  export { SectionOutline };
13
- import { nameParts, partLines, placeOfSection } from "./section-words.js";
14
- import { VISUALLY_HIDDEN } from "./primitives/measure.js";
15
+ import { aboutOf, nameParts, partLines, placeOfSection } from "./section-words.js";
15
16
  /*
16
17
  * WHAT WAS TOLD (Roadmap f-menu-learns): each revision offered is kept as a
17
18
  * telling β€” the words, the view's section it was said on, the slot that
@@ -23,10 +24,15 @@ const toldCard = /* @__PURE__ */ lazyModule(retryingImport(() => import("./told-
23
24
  const Rate = /* @__PURE__ */ toldCard.part((module, props) => _jsx(module.RateTelling, { ...props }), { quiet: true });
24
25
  import "./frame.js"; // A door: tied to what the page drew first (TIED DOORS, packages/primitives/src/frame.ts).
25
26
  export function SectionDrawn({ ask, open, close }) {
26
- if (ask)
27
+ if (ask?.part)
28
+ return _jsx(PageCard, { ask: ask, close: close }, ask.at);
29
+ if (ask?.section)
27
30
  return _jsx(SectionCard, { ask: ask, close: close }, ask.at);
28
31
  return open ? _jsx(SectionOutline, { elements: open }) : null;
29
32
  }
33
+ /* A built-in page's card (Roadmap f-make-any-page-better), fetched the first time it is asked for. */
34
+ const pageCard = /* @__PURE__ */ lazyModule(retryingImport(() => import("./page-better.js")));
35
+ const PageCard = /* @__PURE__ */ pageCard.part((module, props) => _jsx(module.PageCard, { ...props }), { what: "Make this better" });
30
36
  const WIDTH = 360;
31
37
  const GAP = 12;
32
38
  /** The card's place: beside the box where the window has room, else under it, else over it. */
@@ -97,36 +103,59 @@ function partsIn(root, rect, point) {
97
103
  }
98
104
  return outermost;
99
105
  }
100
- /** The words of a revision's findings that keep it from being offered, and the notes said with it. */
101
- function judged(handle, before, live, liveRefused, draft, source) {
102
- const after = draft.drawing();
103
- const revision = compareViewRevision({ source: before, css: live.css, ...(live.root ? { drawing: live.root } : {}), ...(live.height !== undefined ? { height: live.height } : {}) }, { source, css: after.css, ...(after.root ? { drawing: after.root } : {}), ...(after.height !== undefined ? { height: after.height } : {}) });
106
+ /**
107
+ * WHAT A REVISION OF A VIEW BROKE IN ITS DRAWING: a class still drawn that
108
+ * lost every rule styling it, and a part drawn before and not now. A part
109
+ * is found by where it stands, not by its words (Roadmap
110
+ * f-better-ui-draws-first-time): one whose heading was reworded in place
111
+ * is the same part, and is the person's to reword where they asked it of
112
+ * the part being changed (`mayReword`, its headings).
113
+ */
114
+ export function drawingFindings(before, after, options = {}) {
115
+ const side = (one) => ({ source: one.source, css: one.css, ...(one.root ? { drawing: one.root } : {}), ...(one.height !== undefined ? { height: one.height } : {}) });
116
+ const revision = compareViewRevision(side(before), side(after));
104
117
  const findings = [];
105
118
  for (const one of revision.unstyled)
106
119
  findings.push(`The class β€œ${one.class}” is still drawn and no rule styles it now.`);
107
- for (const one of revision.sectionsMissing)
108
- findings.push(`β€œ${one.name}” is not drawn any more.`);
120
+ const names = (one) => (one.root ? sectionsOfDrawing(one.root).map((section) => section.name) : []);
121
+ const missing = before.root && after.root ? lostUnlessReworded(revision.sectionsMissing.map((one) => one.name), names(before), names(after), options) : revision.sectionsMissing.map((one) => one.name);
122
+ for (const name of missing)
123
+ findings.push(`β€œ${name}” is not drawn any more.`);
124
+ return findings;
125
+ }
126
+ /** The words of a revision's findings that keep it from being offered. */
127
+ function judged(before, live, liveRefused, draft, source, options) {
128
+ const after = draft.drawing();
129
+ const findings = drawingFindings({ source: before, css: live.css, root: live.root, height: live.height }, { source, css: after.css, root: after.root, height: after.height }, options);
109
130
  for (const refused of draft.refused())
110
131
  if (!liveRefused.includes(refused))
111
132
  findings.push(refused);
112
- const notes = [];
113
- const height = revision.height;
114
- if (height && Math.abs(height.ratio) >= 0.1)
115
- notes.push(`The view is ${Math.abs(Math.round(height.change))} px ${height.change > 0 ? "taller" : "shorter"} (${Math.round(height.before)} β†’ ${Math.round(height.after)}).`);
116
- void handle;
117
- return { findings, notes };
133
+ return findings;
118
134
  }
119
135
  export function SectionCard({ ask, close }) {
120
- const { ai, aiDesk, principal, keptViews } = useGraview();
136
+ const { ai, aiDesk, aiHere, session, principal, keptViews, store } = useGraview();
121
137
  const [section, setSection] = useState(ask.section);
122
138
  const place = placeOfSection(ask.section);
123
139
  const handle = useRef(viewPlaceOf(place));
124
140
  const draft = useRef(null);
125
- const card = useRef(null);
126
- const field = useRef(null);
127
- const [phase, setPhase] = useState(ask.mode === "highlight" ? { at: "highlighting" } : ask.mode === "describe" ? { at: "asking" } : { at: "working" });
128
- const [words, setWords] = useState("");
141
+ const [phase, setPhase] = useState(ask.mode === "highlight" ? { at: "highlighting" } : { at: "open" });
142
+ /* The view's code before any step, the page as it was read, the drafts that passed, the conversation and this turn's steps. */
129
143
  const original = useRef(undefined);
144
+ const before = useRef(null);
145
+ /* The headings of the part being changed, read where it stood before any step: the ones a person may ask to reword. */
146
+ const partHeadings = useRef(null);
147
+ const steps = useRef([]);
148
+ const talk = useRef([]);
149
+ const events = useRef([]);
150
+ const since = useRef(Date.now());
151
+ const stopping = useRef(null);
152
+ const pointedAt = useRef(null);
153
+ /** What the conversation is about, where the person pointed at a part of the section. */
154
+ const aboutNow = useRef(null);
155
+ const [covering, setCovering] = useState(false);
156
+ const [comparing, setComparing] = useState(false);
157
+ const [, setTick] = useState(0);
158
+ const redraw = useCallback(() => setTick((one) => one + 1), []);
130
159
  /* The telling of the revision on offer, kept with the host's door (`ai.told`), and how it ended. */
131
160
  const [telling, setTelling] = useState(null);
132
161
  const tellingNow = useRef(null);
@@ -135,23 +164,32 @@ export function SectionCard({ ask, close }) {
135
164
  if (now && ai.told)
136
165
  void toldCard.load().then((module) => module.changeTelling(ai.told, now.id, { outcome }), () => undefined);
137
166
  }, [ai.told]);
138
- /** Keeps what was said and decided as a telling: offered as a draft ("previewed"), or refused. */
167
+ /** Keeps what was said and decided as a telling: offered as a draft ("previewed"), or not. */
139
168
  const tell = useCallback((words, on, refused, stages, ms, interaction) => {
140
169
  const keeps = ai.told;
141
170
  if (!keeps)
142
171
  return;
143
- const slot = stages.at(-1)?.stage === "thinking" ? "full" : stages.length > 0 ? "light" : undefined;
144
172
  void toldCard.load().then((module) => {
145
- const kept = module.keepTelling(keeps, { words, target: { on: "place", place: on.view }, decided: refused ? { type: "refusal" } : { type: "view", section: on.name }, ...(slot ? { slot } : {}), stages: stages.map((one) => ({ stage: one.stage === "thinking" ? "full" : "light", ms: one.ms, model: one.model })), ms, outcome: refused ? "abandoned" : "previewed", interaction }, principal);
173
+ const kept = module.keepTelling(keeps, { words, target: { on: "place", place: on.view }, decided: refused ? { type: "refusal" } : { type: "view", section: on.name }, slot: "full", stages: stages.map((one) => ({ stage: one.stage === "thinking" ? "full" : "light", ms: one.ms, model: one.model })), ms, outcome: refused ? "abandoned" : "previewed", interaction }, principal);
146
174
  tellingNow.current = kept ?? null;
147
175
  setTelling(kept ?? null);
148
176
  }, () => undefined);
149
177
  }, [ai.told, principal]);
150
178
  /* Put away: a draft not kept goes with the card, and the view stands as it was. */
151
179
  useEffect(() => () => {
180
+ stopping.current?.abort();
152
181
  draft.current?.dispose();
153
182
  draft.current = null;
154
183
  }, []);
184
+ /* GRAVIEW IS HERE WHILE IT WORKS, as it is beside the quick ask. */
185
+ const working = phase.at === "working";
186
+ useEffect(() => {
187
+ if (!working)
188
+ return;
189
+ aiHere({ participant: `agent:graview-for-${principal.id ?? "anyone"}:${session}`, kind: "agent", name: "Graview", ...(principal.id ? { onBehalfOf: principal.id } : {}), over: null, robot: { at: null, mode: "thinking" } });
190
+ return () => aiHere(null);
191
+ // eslint-disable-next-line react-hooks/exhaustive-deps
192
+ }, [working]);
155
193
  const entryOf = useCallback((said, outcome, stages, extra = {}) => ({
156
194
  id: interactionId(),
157
195
  at: new Date().toISOString(),
@@ -165,95 +203,221 @@ export function SectionCard({ ask, close }) {
165
203
  usage: stages.flatMap((one) => (one.usage ? [{ tier: one.stage === "light" ? "fast" : "thinking", model: one.model, inputTokens: one.usage.inputTokens, outputTokens: one.usage.outputTokens }] : [])),
166
204
  ...extra,
167
205
  }), [principal, section.view]);
168
- /** Revise the section: asked, composed whole, judged as a draft in the view's place, offered or refused. */
169
- const revise = useCallback(async (mode, said, pointed, on = section) => {
206
+ /** The draft drawn in the view's place: the code given, drawn again. */
207
+ const drawDraft = useCallback(async (source) => {
208
+ draft.current?.dispose();
170
209
  const view = handle.current;
171
- const asked = mode === "just" ? `Make β€œ${on.name}” better` : `Make β€œ${on.name}” better: ${said}`;
172
- if (!view)
173
- return setPhase({ at: "refused", said: "This view is not drawn here any more." });
174
- setPhase({ at: "working", ...(pointed ? { pointed } : {}) });
175
- const before = await view.source();
176
- if (before === undefined)
177
- return setPhase({ at: "refused", said: "This view is a whole worker script, and only a plain script's section can be revised here." });
178
- original.current ??= before;
179
- const started = Date.now();
180
- const live = view.drawing();
210
+ const drawn = view.draft(source);
211
+ draft.current = drawn;
212
+ await drawn.drawn;
213
+ return drawn;
214
+ }, []);
215
+ /** The view as the person last saw it: the newest draft that passed, or the view as it was. */
216
+ const showLatest = useCallback(async () => {
217
+ const latest = steps.current.at(-1);
218
+ setComparing(false);
219
+ if (latest)
220
+ await drawDraft(latest.source);
221
+ else {
222
+ draft.current?.dispose();
223
+ draft.current = null;
224
+ }
225
+ }, [drawDraft]);
226
+ const say = useCallback((who, words) => {
227
+ talk.current.push({ who, said: words });
228
+ redraw();
229
+ }, [redraw]);
230
+ const askedWords = () => talk.current.filter((one) => one.who === "person").map((one) => one.said).join(" ");
231
+ /** One turn of the agent on the section: it looks, drafts the whole script with the section changed, is told what the checks found, and drafts again. */
232
+ const askFor = useCallback(async (words) => {
233
+ const view = handle.current;
234
+ if (!view || phase.at === "working")
235
+ return;
236
+ const pointed = pointedAt.current;
237
+ pointedAt.current = null;
238
+ // The person's words as they typed them: what they are about is said beside them, never glued on.
239
+ if (pointed)
240
+ aboutNow.current = pointed;
241
+ say("person", words);
242
+ events.current = [];
243
+ since.current = Date.now();
244
+ setPhase({ at: "working" });
245
+ const live = await view.source();
246
+ if (live === undefined) {
247
+ say("graview", "This view can't be changed here.");
248
+ return setPhase({ at: "open" });
249
+ }
250
+ original.current ??= live;
251
+ const liveDrawing = view.drawing();
181
252
  const liveRefused = view.refused();
182
- const theme = await view.theme().catch(() => ({}));
183
- const result = await reviseSection({
253
+ // Each record by the name its kind gives it, or its link's own words: never a card's words run together.
254
+ const named = (id) => {
255
+ const node = store.graph.getNode(id);
256
+ return node ? labelOf(store.schema.tryDefinition(node.kind), node) : undefined;
257
+ };
258
+ before.current ??= readDrawn(liveDrawing.root?.parentElement ?? liveDrawing.root ?? place, recordLabelsOf(liveDrawing.root, named));
259
+ partHeadings.current ??= headingsOf(section.elements);
260
+ const was = before.current;
261
+ const mayReword = partHeadings.current;
262
+ const stop = new AbortController();
263
+ stopping.current = stop;
264
+ const started = Date.now();
265
+ const turnBegan = steps.current.length;
266
+ const result = await runDesignAgent({
184
267
  ai,
185
- source: before,
186
- section: on.name,
187
- mode,
188
- ...(said ? { words: said } : {}),
189
- ...(pointed ? { highlighted: [pointed.name, ...pointed.lines] } : {}),
190
- theme,
191
- shows: shownBy(on.elements),
192
- judge: async (source) => {
193
- draft.current?.dispose();
194
- draft.current = null;
195
- const refused = await view.check(source);
196
- if (refused.length > 0)
197
- return refused;
198
- const drawn = view.draft(source);
199
- draft.current = drawn;
200
- const { failed } = await drawn.drawn;
201
- if (failed)
202
- return [`The view did not draw: ${failed}`];
203
- await new Promise((settled) => requestAnimationFrame(() => requestAnimationFrame(settled)));
204
- return judged(view, before, live, liveRefused, drawn, source).findings;
268
+ part: { name: section.name, slot: "place" },
269
+ manifest: { name: view.name, attach: "view", cardinality: "many" },
270
+ talk: talk.current,
271
+ keep: { title: "", headings: was.headings, records: was.records.map((one) => one.label), says: Object.fromEntries(was.records.flatMap((one) => (one.facts?.length || one.under ? [[one.label, { ...(one.facts?.length ? { shows: one.facts } : {}), ...(one.under ? { under: one.under } : {}) }]] : []))) },
272
+ current: steps.current.at(-1)?.source ?? live,
273
+ section: section.name,
274
+ rewordable: mayReword,
275
+ noun: "records",
276
+ ...(aboutNow.current ? { about: aboutNow.current } : {}),
277
+ host: {
278
+ look: () => lookOf(readDrawn(view.drawing().root?.parentElement ?? place, was.records.map((one) => one.label))),
279
+ read: () => ({ shows: shownBy(section.elements) }),
280
+ draft: async (source) => {
281
+ setCovering(true);
282
+ try {
283
+ const refused = await view.check(source);
284
+ if (refused.length > 0) {
285
+ await showLatest();
286
+ return { findings: refused, drew: undefined };
287
+ }
288
+ const drawn = await drawDraft(source);
289
+ const { failed } = await drawn.drawn;
290
+ if (failed) {
291
+ await showLatest();
292
+ return { findings: [`The view did not draw: ${failed}`], drew: undefined };
293
+ }
294
+ await new Promise((settled) => requestAnimationFrame(() => requestAnimationFrame(settled)));
295
+ const root = drawn.drawing().root;
296
+ const now = readDrawn(root?.parentElement ?? root, was.records.map((one) => one.label));
297
+ // And every control it draws pressed, headless and nothing applied: one that does nothing, throws or is refused goes back to the agent.
298
+ const pressed = view.presses ? await view.presses(source).catch(() => undefined) : undefined;
299
+ const asked = { asked: askedWords(), mayReword };
300
+ const findings = [...judged(original.current, liveDrawing, liveRefused, drawn, source, asked), ...drawnFindings(was, now, asked), ...(pressed?.problems ?? []).slice(0, 5).map((one) => `${one.control}: ${one.says}`)];
301
+ if (findings.length > 0)
302
+ await showLatest();
303
+ else {
304
+ steps.current.push({ source, said: describeChange(section.name, was, now) ?? `β€œ${section.name}” has changed. Compare Before and After.`, entry: entryOf(`Make β€œ${section.name}” better: ${askedWords()}`, "offered", []) });
305
+ setComparing(false);
306
+ }
307
+ return { findings, drew: lookOf(now) };
308
+ }
309
+ finally {
310
+ setCovering(false);
311
+ }
312
+ },
313
+ },
314
+ onEvent: (event) => {
315
+ // The words as the model writes them stand in for the step it is on, and give way to the step once it is said.
316
+ if (events.current.at(-1)?.kind === "streaming")
317
+ events.current.pop();
318
+ events.current.push(event);
319
+ redraw();
205
320
  },
321
+ signal: stop.signal,
206
322
  });
207
- if (!result.ok) {
208
- draft.current?.dispose();
209
- draft.current = null;
210
- const failed = entryOf(asked, "failed", result.stages, { failed: result.refused });
211
- aiDesk.record(failed);
212
- tell(said || asked, on, true, result.stages, Date.now() - started, failed.id);
213
- return setPhase({ at: "refused", said: result.refused });
214
- }
215
- const notes = draft.current ? judged(view, before, live, liveRefused, draft.current, result.source).notes : [];
216
- const entry = entryOf(asked, "offered", result.stages);
217
- tell(said || asked, on, false, result.stages, Date.now() - started, entry.id);
218
- setPhase({ at: "draft", source: result.source, said: result.said, notes: result.repaired ? ["Repaired once after the first try broke the view.", ...notes] : notes, entry });
219
- }, [ai, aiDesk, entryOf, section, tell]);
220
- /* Just do it: at once. */
323
+ if (stopping.current !== stop)
324
+ return;
325
+ stopping.current = null;
326
+ if (events.current.at(-1)?.kind === "streaming")
327
+ events.current.pop();
328
+ const offered = steps.current.length > turnBegan;
329
+ const entry = entryOf(`Make β€œ${section.name}” better: ${askedWords()}`, offered ? "offered" : "failed", result.stages, offered ? {} : { failed: result.last?.findings.join(" ") ?? result.said });
330
+ aiDesk.record(entry);
331
+ tell(askedWords(), section, !offered, result.stages, Date.now() - started, entry.id);
332
+ // What a control does is the press check's to say, never the model's.
333
+ say("graview", result.passed && CONTROL_WORDS.test(result.said) ? "Here it is on the page." : result.said);
334
+ // Two drafts failed the same way: the person chooses β€” keep the last one anyway, where it drew, or stop.
335
+ if (result.ended === "stuck" && !offered)
336
+ return setPhase({ at: "stuck", ...(result.keepable && result.last ? { source: result.last.source } : {}) });
337
+ setPhase(steps.current.length > 0 ? { at: "draft" } : { at: "open" });
338
+ }, [ai, aiDesk, drawDraft, entryOf, phase.at, place, redraw, say, section, showLatest, store, tell]);
339
+ /** Stop: at once β€” the turn's drafts gone, the view as it was before it began. */
340
+ const stopTurn = useCallback(() => {
341
+ if (phase.at !== "working")
342
+ return;
343
+ const stop = stopping.current;
344
+ stopping.current = null;
345
+ stop?.abort();
346
+ setCovering(false);
347
+ const began = new Date(since.current).toISOString();
348
+ steps.current = steps.current.filter((one) => one.entry.at < began);
349
+ void showLatest();
350
+ say("graview", "Stopped. The view is as it was.");
351
+ setPhase(steps.current.length > 0 ? { at: "draft" } : { at: "open" });
352
+ }, [phase.at, say, showLatest]);
353
+ /* Words that came with the ask β€” typed in the menu, or a suggestion taken β€” are asked at once. */
221
354
  const began = useRef(false);
222
355
  useEffect(() => {
223
- if (ask.mode !== "just" || began.current)
356
+ if (began.current || !(ask.mode === "just" || (ask.mode === "describe" && ask.words)))
224
357
  return;
225
358
  began.current = true;
226
- void revise("just", "");
227
- }, [ask.mode, revise]);
228
- const keep = useCallback(async () => {
229
- if (phase.at !== "draft" || !keptViews?.revise || !handle.current)
359
+ void askFor(ask.mode === "just" ? "Make it easier to read at a glance" : ask.words);
360
+ }, [ask.mode, ask.words, askFor]);
361
+ const current = steps.current.at(-1);
362
+ const compare = useCallback(async (originalShown) => {
363
+ if (!current || originalShown === comparing)
364
+ return;
365
+ setComparing(originalShown);
366
+ if (originalShown) {
367
+ draft.current?.dispose();
368
+ draft.current = null;
369
+ }
370
+ else
371
+ await drawDraft(current.source);
372
+ }, [current, comparing, drawDraft]);
373
+ const keep = useCallback(async (anyway) => {
374
+ // Kept anyway: the last draft of a turn that failed the same way twice, as the person chose.
375
+ if (anyway !== undefined && phase.at === "stuck")
376
+ steps.current.push({ source: anyway, said: `β€œ${section.name}” kept as it was last drafted.`, entry: entryOf(`Make β€œ${section.name}” better: ${askedWords()}`, "offered", []) });
377
+ const latest = steps.current.at(-1);
378
+ if ((phase.at !== "draft" && !(anyway !== undefined && phase.at === "stuck")) || !latest || !keptViews?.revise || !handle.current)
230
379
  return;
231
380
  const view = handle.current;
232
- const before = (await view.source()) ?? "";
233
- setPhase({ at: "keeping", source: phase.source, entry: phase.entry });
381
+ const was = (await view.source()) ?? "";
382
+ setPhase({ at: "keeping" });
234
383
  try {
235
- const answer = await keptViews.revise({ name: view.name, title: view.title, source: phase.source, before, section: section.name, said: phase.said });
384
+ const answer = await keptViews.revise({ name: view.name, title: view.title, source: latest.source, before: was, section: section.name, said: latest.said });
236
385
  draft.current?.dispose();
237
386
  draft.current = null;
238
- view.draw(phase.source);
239
- const entry = { ...phase.entry, outcome: "accepted" };
240
- aiDesk.record(entry);
387
+ view.draw(latest.source);
388
+ aiDesk.record({ ...latest.entry, outcome: "accepted" });
241
389
  ended("accepted");
242
- setPhase({ at: "kept", source: before, ...(answer && answer.note ? { note: answer.note } : {}), answer, entry });
390
+ steps.current = [];
391
+ setPhase({ at: "kept", source: was, ...(answer && answer.note ? { note: answer.note } : {}), answer, entry: latest.entry });
243
392
  }
244
- catch (error) {
245
- setPhase({ at: "refused", said: error instanceof Error ? error.message : "The host did not keep it." });
393
+ catch {
394
+ say("graview", "It couldn't be kept.");
395
+ if (anyway !== undefined) {
396
+ steps.current.pop();
397
+ return setPhase(steps.current.length > 0 ? { at: "draft" } : { at: "open" });
398
+ }
399
+ setPhase({ at: "draft" });
246
400
  }
247
- }, [phase, keptViews, aiDesk, section.name, ended]);
401
+ // eslint-disable-next-line react-hooks/exhaustive-deps
402
+ }, [phase, keptViews, aiDesk, section.name, ended, say, entryOf]);
403
+ /** Stop, where two drafts failed the same way: nothing kept, the view as it was. */
404
+ const stopStuck = useCallback(() => {
405
+ say("graview", "Stopped. The view is as it was.");
406
+ setPhase(steps.current.length > 0 ? { at: "draft" } : { at: "open" });
407
+ }, [say]);
248
408
  const discard = useCallback(() => {
249
409
  draft.current?.dispose();
250
410
  draft.current = null;
251
- if (phase.at === "draft") {
252
- aiDesk.record({ ...phase.entry, outcome: "dismissed" });
411
+ const latest = steps.current.at(-1);
412
+ if (latest) {
413
+ aiDesk.record({ ...latest.entry, outcome: "dismissed" });
253
414
  ended("declined");
254
415
  }
255
- close();
256
- }, [phase, aiDesk, close, ended]);
416
+ steps.current = [];
417
+ setComparing(false);
418
+ say("graview", "Put back as it was.");
419
+ setPhase({ at: "open" });
420
+ }, [aiDesk, ended, say]);
257
421
  const undo = useCallback(async () => {
258
422
  if (phase.at !== "kept" || !handle.current)
259
423
  return;
@@ -268,61 +432,44 @@ export function SectionCard({ ask, close }) {
268
432
  aiDesk.record({ ...phase.entry, outcome: "dismissed" });
269
433
  // A quiet sign, never a πŸ‘Ž.
270
434
  ended("undone");
435
+ say("graview", "Undone. The view is as it was.");
271
436
  setPhase({ at: "undone" });
272
437
  }
273
- catch (error) {
274
- setPhase({ at: "refused", said: error instanceof Error ? error.message : "The host did not put it back." });
438
+ catch {
439
+ say("graview", "It couldn't be put back.");
275
440
  }
276
- }, [phase, keptViews, aiDesk, section.name, ended]);
277
- /* Placed beside the section, kept there as the page moves. */
278
- useLayoutEffect(() => {
279
- let frame = 0;
280
- const follow = () => {
281
- if (card.current) {
282
- const drafted = place?.querySelector("[data-graview-section-draft]");
283
- placeBeside(card.current, drafted ? boxOf([drafted]) : boxOf(section.elements) ?? (place ? boxOf([place]) : null));
284
- }
285
- frame = requestAnimationFrame(follow);
286
- };
287
- follow();
288
- return () => cancelAnimationFrame(frame);
289
- }, [section, place]);
290
- /* The keyboard: in the field when it asks, else on the card's first button. */
291
- useEffect(() => {
292
- if (phase.at === "asking")
293
- field.current?.focus();
294
- else if (phase.at !== "highlighting")
295
- card.current?.querySelector("button[data-first]")?.focus();
296
- }, [phase.at]);
297
- /* Escape puts it away: a draft is discarded, the keyboard goes back where it was. */
441
+ }, [phase, keptViews, aiDesk, section.name, ended, say]);
442
+ /* Escape: Stop while it works, else put it away. */
298
443
  useEffect(() => {
299
444
  const key = (event) => {
300
- if (event.key !== "Escape")
445
+ if (event.key !== "Escape" || phase.at === "highlighting")
301
446
  return;
302
447
  event.preventDefault();
303
- discard();
448
+ if (phase.at === "working")
449
+ return stopTurn();
450
+ if (steps.current.length > 0)
451
+ ended("declined");
452
+ close();
304
453
  ask.returnTo?.focus?.();
305
454
  };
306
455
  document.addEventListener("keydown", key);
307
456
  return () => document.removeEventListener("keydown", key);
308
- }, [discard, ask.returnTo]);
309
- const pointed = phase.at === "asking" || phase.at === "working" ? phase.pointed : undefined;
457
+ }, [phase.at, stopTurn, close, ended, ask.returnTo]);
310
458
  if (phase.at === "highlighting") {
311
- return (_jsx(Highlighter, { place: place, section: section, cancel: close, chose: (elements) => {
459
+ return (_jsx(Highlighter, { box: place, root: place?.querySelector("[data-worker-view]")?.shadowRoot?.querySelector("[data-graview-view-root]") ?? undefined, whole: section.elements, cancel: () => setPhase({ at: "open" }), chose: (elements) => {
312
460
  const first = elements[0];
313
461
  const under = first ? sectionUnderNow(first) : undefined;
314
- const on = under ?? section;
315
- setSection(on);
316
- setPhase({ at: "asking", pointed: { name: nameParts(elements.map(readPart)), lines: partLines(elements.map(readPart)) } });
462
+ setSection(under ?? section);
463
+ pointedAt.current = { name: aboutOf(elements) ?? nameParts(elements.map(readPart)), lines: partLines(elements.map(readPart)) };
464
+ setPhase({ at: "open" });
317
465
  } }));
318
466
  }
319
- const drafted = phase.at === "draft" || phase.at === "keeping";
320
- const outlined = drafted ? [...(place?.querySelectorAll("[data-graview-section-draft]") ?? [])] : phase.at === "kept" || phase.at === "undone" ? [] : section.elements;
321
- return (_jsxs(_Fragment, { children: [outlined.length > 0 ? _jsx(SectionOutline, { elements: outlined, label: drafted ? "Draft" : undefined }) : null, _jsxs("div", { ref: card, role: "dialog", "aria-label": `Make β€œ${section.name}” better`, "data-testid": "section-card", "data-section-phase": phase.at, "data-graview-popover": "", style: CARD, children: [_jsxs("p", { style: HEAD, children: ["Make ", _jsxs("strong", { children: ["\u201C", section.name, "\u201D"] }), " better"] }), pointed ? (_jsxs("p", { "data-testid": "section-pointed", style: MUTED, children: ["You pointed at ", pointed.name, "."] })) : null, phase.at === "asking" ? (_jsxs("form", { onSubmit: (event) => {
322
- event.preventDefault();
323
- if (words.trim())
324
- void revise(ask.mode, words.trim(), phase.pointed);
325
- }, style: { display: "grid", gap: 8 }, children: [_jsx("label", { style: VISUALLY_HIDDEN, htmlFor: "graview-section-words", children: phase.pointed ? "What should change here?" : "What should change?" }), _jsx("input", { id: "graview-section-words", ref: field, "data-testid": "section-words", value: words, onChange: (event) => setWords(event.target.value), placeholder: phase.pointed ? "What should change here?" : "What should change?", style: FIELD }), _jsxs("div", { style: ROW, children: [_jsx("button", { type: "submit", "data-first": "", disabled: !words.trim(), style: PRIMARY, children: "Revise" }), _jsx("button", { type: "button", onClick: close, style: QUIET, children: "Cancel" })] })] })) : null, _jsx("p", { role: "status", "aria-live": "polite", "data-testid": "section-status", style: phase.at === "working" || phase.at === "keeping" ? THINKING : MUTED, children: phase.at === "working" ? `Revising β€œ${section.name}”…` : phase.at === "keeping" ? "Keeping it…" : phase.at === "draft" ? phase.said : phase.at === "kept" ? `Kept.${phase.note ? ` ${phase.note}` : ""}` : phase.at === "undone" ? "Put back as it was." : phase.at === "refused" ? phase.said : "" }), phase.at === "draft" ? (_jsxs(_Fragment, { children: [_jsx("p", { "data-testid": "section-draft-line", style: MUTED, children: "Draft \u2014 shown in place, not kept." }), phase.notes.map((note) => (_jsx("p", { "data-testid": "section-note", style: MUTED, children: note }, note))), _jsxs("div", { style: ROW, children: [_jsx("button", { type: "button", "data-first": "", "data-testid": "section-keep", onClick: () => void keep(), style: PRIMARY, children: "Keep" }), _jsx("button", { type: "button", "data-testid": "section-discard", onClick: discard, style: QUIET, children: "Discard" })] })] })) : null, phase.at === "kept" ? (_jsxs("div", { style: ROW, children: [_jsx("button", { type: "button", "data-first": "", "data-testid": "section-undo", onClick: () => void undo(), style: QUIET, children: "Undo" }), _jsx("button", { type: "button", onClick: close, style: QUIET, children: "Close" })] })) : null, phase.at === "refused" || phase.at === "undone" ? (_jsx("div", { style: ROW, children: _jsx("button", { type: "button", "data-first": "", onClick: close, style: QUIET, children: "Close" }) })) : null, telling && ai.told && phase.at !== "working" && phase.at !== "asking" ? _jsx(Rate, { told: ai.told, telling: telling }) : null] })] }));
467
+ return (_jsxs(_Fragment, { children: [section.elements.length > 0 && phase.at === "open" && talk.current.length === 0 ? _jsx(SectionOutline, { elements: section.elements }) : null, covering ? _jsx(Redesigning, { over: place, name: section.name, said: `Graview is drafting β€œ${section.name}”…` }) : null, _jsx(BetterPanel, { name: section.name, scope: `This changes how β€œ${section.name}” looks for everyone in this app.`, phase: phase.at, talk: talk.current, steps: events.current, since: since.current, working: phase.at === "working" || phase.at === "keeping", ...(current && phase.at === "draft" ? { draft: { said: current.said, before: comparing } } : {}), ...(phase.at === "kept" ? { kept: { ...(phase.note ? { note: phase.note } : {}), undo: () => void undo() } } : {}), ...(phase.at === "stuck" ? { stuck: { ...(phase.source !== undefined ? { keepAnyway: () => void keep(phase.source) } : {}), stop: stopStuck } } : {}), about: (pointedAt.current ?? aboutNow.current)?.name ?? section.name, ask: (words) => void askFor(words), just: () => void askFor("Make it easier to read at a glance"), point: () => setPhase({ at: "highlighting" }), stop: stopTurn, compare: (originalShown) => void compare(originalShown), keep: () => void keep(), discard: discard, close: () => {
468
+ stopping.current?.abort();
469
+ if (steps.current.length > 0)
470
+ ended("declined");
471
+ close();
472
+ }, children: telling && ai.told && phase.at !== "working" ? _jsx(Rate, { told: ai.told, telling: telling }) : null })] }));
326
473
  }
327
474
  /**
328
475
  * HIGHLIGHT AREA: an overlay over the view. A drag draws a rectangle and
@@ -330,10 +477,9 @@ export function SectionCard({ ask, close }) {
330
477
  * or "Cancel", puts it away; "The whole section" takes the section itself
331
478
  * for a keyboard.
332
479
  */
333
- function Highlighter({ place, section, cancel, chose }) {
480
+ export function Highlighter({ box: place, root, whole, wholeSaid = "The whole section", cancel, chose }) {
334
481
  const cover = useRef(null);
335
482
  const [drag, setDrag] = useState(null);
336
- const root = place?.querySelector("[data-worker-view]")?.shadowRoot?.querySelector("[data-graview-view-root]") ?? undefined;
337
483
  useLayoutEffect(() => {
338
484
  let frame = 0;
339
485
  const follow = () => {
@@ -377,8 +523,9 @@ function Highlighter({ place, section, cancel, chose }) {
377
523
  event.preventDefault();
378
524
  event.currentTarget.setPointerCapture?.(event.pointerId);
379
525
  setDrag({ x0: event.clientX, y0: event.clientY, x1: event.clientX, y1: event.clientY });
380
- }, onPointerMove: (event) => setDrag((was) => (was ? { ...was, x1: event.clientX, y1: event.clientY } : was)), onPointerUp: end, onPointerCancel: () => setDrag(null), style: COVER, children: [_jsxs("p", { style: COVER_HINT, children: ["Drag over the part to change, or click one \u00B7", " ", _jsx("button", { type: "button", onPointerDown: (event) => event.stopPropagation(), onClick: () => chose(section.elements), style: LINK, children: "The whole section" }), " ", "\u00B7", " ", _jsx("button", { type: "button", onPointerDown: (event) => event.stopPropagation(), onClick: cancel, style: LINK, children: "Cancel" })] }), box ? _jsx("div", { "data-testid": "section-drag", style: { ...DRAG, left: box.left - (cover.current?.getBoundingClientRect().left ?? 0), top: box.top - (cover.current?.getBoundingClientRect().top ?? 0), width: box.width, height: box.height } }) : null] }));
526
+ }, onPointerMove: (event) => setDrag((was) => (was ? { ...was, x1: event.clientX, y1: event.clientY } : was)), onPointerUp: end, onPointerCancel: () => setDrag(null), style: COVER, children: [_jsxs("p", { style: COVER_HINT, children: ["Drag over the part to change, or click one \u00B7", " ", _jsx("button", { type: "button", onPointerDown: (event) => event.stopPropagation(), onClick: () => chose(whole), style: LINK, children: wholeSaid }), " ", "\u00B7", " ", _jsx("button", { type: "button", onPointerDown: (event) => event.stopPropagation(), onClick: cancel, style: LINK, children: "Cancel" })] }), box ? _jsx("div", { "data-testid": "section-drag", style: { ...DRAG, left: box.left - (cover.current?.getBoundingClientRect().left ?? 0), top: box.top - (cover.current?.getBoundingClientRect().top ?? 0), width: box.width, height: box.height } }) : null] }));
381
527
  }
528
+ export { readPart, placeBeside };
382
529
  const CARD = {
383
530
  position: "fixed",
384
531
  zIndex: layer("popover"),
@@ -408,4 +555,6 @@ const PAD = 12;
408
555
  const COVER = { userSelect: "none", WebkitUserSelect: "none", position: "fixed", zIndex: layer("popover"), boxSizing: "border-box", cursor: "crosshair", touchAction: "none", outline: "2px dashed var(--graview-accent)", background: "color-mix(in srgb, var(--graview-accent) 6%, transparent)" };
409
556
  const COVER_HINT = { position: "absolute", left: 8, top: 8, margin: 0, padding: "4px 10px", borderRadius: 999, fontSize: "0.8125rem", color: "var(--graview-ink)", background: "var(--graview-float, var(--graview-panel))", border: "1px solid var(--graview-edge)", boxShadow: "var(--graview-lift-low)", cursor: "default" };
410
557
  const DRAG = { position: "absolute", border: "2px solid var(--graview-accent)", background: "color-mix(in srgb, var(--graview-accent) 14%, transparent)", borderRadius: 4, pointerEvents: "none" };
558
+ /** The card's look, shared with a built-in page's card. */
559
+ export const LOOK = { CARD, HEAD, MUTED, THINKING, ROW, FIELD, PRIMARY, QUIET };
411
560
  //# sourceMappingURL=section-better.js.map