@north-light/crouter 0.3.197 → 0.3.199
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/api/client.d.ts +19 -19
- package/dist/api/client.js +29 -37
- package/dist/api/dto/broker-ops.d.ts +3 -1
- package/dist/api/dto/broker.d.ts +13 -2
- package/dist/api/dto/broker.js +4 -4
- package/dist/api/dto/human.d.ts +6 -5
- package/dist/api/dto/inbox.d.ts +126 -33
- package/dist/api/dto/nodes.d.ts +8 -2
- package/dist/api/dto/profiles.d.ts +23 -0
- package/dist/api/dto/profiles.js +2 -2
- package/dist/api/dto/worktree.d.ts +3 -5
- package/dist/api/routes.d.ts +3 -5
- package/dist/api/routes.js +4 -6
- package/dist/builtin-memory/00-runtime-base.md +8 -8
- package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
- package/dist/builtin-memory/02-lifecycle/00-terminal.md +3 -3
- package/dist/builtin-memory/04-orchestration-kernel.md +6 -6
- package/dist/builtin-memory/05-kinds/design/00-base.md +1 -1
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +2 -2
- package/dist/builtin-memory/05-kinds/review/00-base.md +2 -2
- package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -1
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +1 -1
- package/dist/builtin-memory/insights/capture.md +2 -2
- package/dist/builtin-memory/insights/init.md +3 -3
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/memory-loading.md +1 -1
- package/dist/builtin-memory/internal/nodes-and-canvas.md +3 -3
- package/dist/builtin-memory/internal/plugins.md +59 -2
- package/dist/builtin-memory/internal/storage-tiers.md +3 -1
- package/dist/builtin-memory/spec/roadmap.md +2 -2
- package/dist/builtin-memory/testing.md +1 -1
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +1 -1
- package/dist/clients/attach/chrome/bash-jobs.js +3 -3
- package/dist/clients/attach/render/chat-view.d.ts +29 -9
- package/dist/clients/attach/render/chat-view.js +64 -35
- package/dist/clients/attach/render/markdown-source.js +0 -6
- package/dist/clients/attach/render/page-block.d.ts +34 -0
- package/dist/clients/attach/render/page-block.js +226 -0
- package/dist/clients/attach/render/tool-calls.js +16 -2
- package/dist/clients/attach/session/whip.d.ts +2 -0
- package/dist/clients/attach/session/whip.js +2 -0
- package/dist/clients/attach/viewer.js +1419 -982
- package/dist/clients/conversation/projection.js +18 -3
- package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +9 -6
- package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +13 -14
- package/dist/clients/inbox/controller.js +10 -16
- package/dist/clients/inbox/page-adapter.d.ts +3 -4
- package/dist/clients/inbox/page-adapter.js +3 -4
- package/dist/clients/inbox/resolve.d.ts +1 -1
- package/dist/clients/inbox/resolve.js +3 -3
- package/dist/clients/inbox/review/document-surface.js +3 -1
- package/dist/clients/inbox/review/state.d.ts +2 -1
- package/dist/clients/inbox/review/state.js +5 -16
- package/dist/clients/inbox/tui/input.js +37 -19
- package/dist/clients/inbox/tui/panel.js +5 -31
- package/dist/clients/inbox/tui/render.d.ts +11 -0
- package/dist/clients/inbox/tui/render.js +101 -36
- package/dist/clients/inbox/tui/slots.d.ts +4 -4
- package/dist/clients/inbox/tui/slots.js +19 -44
- package/dist/clients/inbox/tui/types.d.ts +6 -10
- package/dist/clients/inbox/tui.js +2 -2
- package/dist/commands/__tests__/human.test.js +118 -24
- package/dist/commands/__tests__/node-message.test.js +14 -0
- package/dist/commands/attention.js +4 -4
- package/dist/commands/canvas-browse.js +1 -1
- package/dist/commands/canvas-snapshot.js +1 -1
- package/dist/commands/canvas.js +1 -1
- package/dist/commands/dashboard.js +1 -1
- package/dist/commands/human/{doc.d.ts → components.d.ts} +1 -1
- package/dist/commands/human/components.js +77 -0
- package/dist/commands/human/feedback.d.ts +2 -0
- package/dist/commands/human/feedback.js +108 -0
- package/dist/commands/human/prompts.d.ts +17 -8
- package/dist/commands/human/prompts.js +217 -226
- package/dist/commands/human/queue.js +57 -88
- package/dist/commands/human/shared.d.ts +4 -15
- package/dist/commands/human/shared.js +19 -61
- package/dist/commands/human.js +32 -19
- package/dist/commands/memory/origin.js +2 -2
- package/dist/commands/node/bash.js +4 -4
- package/dist/commands/node/inspect.js +3 -3
- package/dist/commands/node/lifecycle.js +1 -1
- package/dist/commands/node/message.js +2 -2
- package/dist/commands/node-worktree.js +8 -8
- package/dist/commands/pkg/browse.js +1 -1
- package/dist/commands/pkg/plugin-manage.js +129 -10
- package/dist/commands/profile/delete.js +41 -15
- package/dist/commands/profile.js +1 -1
- package/dist/commands/push.js +1 -1
- package/dist/commands/surface/node/focus.js +29 -14
- package/dist/commands/surface-edit.js +3 -3
- package/dist/commands/surface-inbox.d.ts +2 -0
- package/dist/commands/{human/inbox.js → surface-inbox.js} +14 -18
- package/dist/commands/surface.js +2 -1
- package/dist/commands/sys/doctor.js +60 -3
- package/dist/commands/sys/feedback.js +79 -25
- package/dist/commands/sys/settings.js +1 -1
- package/dist/core/__tests__/cron-broker-capacity.test.js +1 -1
- package/dist/core/__tests__/daemon-boot.test.js +7 -4
- package/dist/core/__tests__/dead-node-policy-table.test.js +20 -36
- package/dist/core/__tests__/extension-abort.test.js +5 -2
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +13 -2
- package/dist/core/__tests__/fixtures/fake-engine.js +47 -3
- package/dist/core/__tests__/human-cancel-guard.test.js +31 -10
- package/dist/core/__tests__/human-deliver.test.js +41 -45
- package/dist/core/__tests__/human-node-not-supervised.test.js +1 -1
- package/dist/core/__tests__/plugin-page-components.test.js +157 -0
- package/dist/core/__tests__/seam/broker-crash-teardown.test.js +13 -11
- package/dist/core/__tests__/seam/dormancy-release.test.js +103 -4
- package/dist/core/__tests__/serial/flagship-lifecycle.test.js +2 -2
- package/dist/core/__tests__/serial/human-deliver-e2e.test.js +12 -16
- package/dist/core/__tests__/serial/live-mutation.test.js +1 -1
- package/dist/core/__tests__/serial/tmux-surface.test.js +4 -2
- package/dist/core/__tests__/serial/worktree.test.js +24 -198
- package/dist/core/__tests__/session-cycles.test.js +22 -0
- package/dist/core/__tests__/stop-guard.test.js +4 -4
- package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +19 -4
- package/dist/core/bash-jobs.d.ts +6 -0
- package/dist/core/bash-jobs.js +10 -0
- package/dist/core/canvas/__tests__/attention.test.js +8 -5
- package/dist/core/canvas/__tests__/render-remote.test.js +5 -5
- package/dist/core/canvas/attention.js +8 -6
- package/dist/core/canvas/browse/app.d.ts +1 -1
- package/dist/core/canvas/browse/app.js +21 -3
- package/dist/core/canvas/browse/model.d.ts +8 -2
- package/dist/core/canvas/browse/model.js +11 -4
- package/dist/core/canvas/browse/render.d.ts +4 -0
- package/dist/core/canvas/browse/render.js +5 -1
- package/dist/core/canvas/canvas.d.ts +33 -0
- package/dist/core/canvas/canvas.js +92 -0
- package/dist/core/canvas/crons.d.ts +9 -0
- package/dist/core/canvas/crons.js +51 -0
- package/dist/core/canvas/node-order.d.ts +22 -1
- package/dist/core/canvas/node-order.js +26 -1
- package/dist/core/canvas/render-source.d.ts +5 -0
- package/dist/core/canvas/render-source.js +1 -0
- package/dist/core/canvas/types.d.ts +9 -3
- package/dist/core/command-plugins/bundle.d.ts +6 -1
- package/dist/core/command-plugins/bundle.js +26 -13
- package/dist/core/command.js +8 -12
- package/dist/core/config.d.ts +47 -1
- package/dist/core/config.js +138 -4
- package/dist/core/help.d.ts +3 -0
- package/dist/core/help.js +1 -1
- package/dist/core/human/__tests__/page-catalog.test.d.ts +1 -0
- package/dist/core/human/__tests__/page-catalog.test.js +20 -0
- package/dist/core/human/__tests__/page-tickets.test.js +68 -34
- package/dist/core/human/__tests__/page.test.js +60 -119
- package/dist/core/human/__tests__/serial/inbox-core.test.js +6 -16
- package/dist/core/human/answer-text.d.ts +3 -0
- package/dist/core/human/answer-text.js +98 -0
- package/dist/core/human/answer.d.ts +53 -0
- package/dist/core/human/answer.js +119 -0
- package/dist/core/human/component-docs.d.ts +18 -0
- package/dist/core/human/component-docs.js +434 -0
- package/dist/core/human/convention.d.ts +17 -3
- package/dist/core/human/convention.js +36 -5
- package/dist/core/human/feedback-companion.d.ts +20 -0
- package/dist/core/human/feedback-companion.js +98 -0
- package/dist/core/human/feedback.d.ts +65 -0
- package/dist/core/human/feedback.js +116 -0
- package/dist/core/human/page-catalog.d.ts +56 -5
- package/dist/core/human/page-catalog.js +151 -22
- package/dist/core/human/page-errors.d.ts +4 -0
- package/dist/core/human/page-errors.js +7 -0
- package/dist/core/human/page-eval.d.ts +20 -0
- package/dist/core/human/page-eval.js +123 -0
- package/dist/core/human/page-schema.d.ts +48 -116
- package/dist/core/human/page-schema.js +100 -72
- package/dist/core/human/page-synth.d.ts +2 -4
- package/dist/core/human/page-synth.js +20 -7
- package/dist/core/human/page.d.ts +13 -21
- package/dist/core/human/page.js +147 -391
- package/dist/core/human/review-schema.d.ts +1 -0
- package/dist/core/human/review-schema.js +1 -1
- package/dist/core/human/root.d.ts +6 -2
- package/dist/core/human/root.js +12 -4
- package/dist/core/human/scan.d.ts +3 -3
- package/dist/core/human/scan.js +21 -21
- package/dist/core/human/summary.d.ts +2 -6
- package/dist/core/human/summary.js +3 -59
- package/dist/core/human/tickets.d.ts +22 -15
- package/dist/core/human/tickets.js +90 -28
- package/dist/core/human/types.d.ts +14 -2
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +1 -0
- package/dist/core/preview-registry.js +8 -10
- package/dist/core/profiles/default-binding.d.ts +4 -0
- package/dist/core/profiles/default-binding.js +24 -0
- package/dist/core/profiles/deletion-reservation.d.ts +8 -0
- package/dist/core/profiles/deletion-reservation.js +38 -0
- package/dist/core/profiles/manifest.d.ts +3 -0
- package/dist/core/profiles/manifest.js +12 -0
- package/dist/core/profiles/select.js +2 -2
- package/dist/core/review/realize.js +12 -4
- package/dist/core/review/store.d.ts +6 -0
- package/dist/core/review/store.js +31 -0
- package/dist/core/runtime/bearings.d.ts +0 -4
- package/dist/core/runtime/bearings.js +4 -62
- package/dist/core/runtime/bin-contributions.d.ts +44 -0
- package/dist/core/runtime/bin-contributions.js +186 -0
- package/dist/core/runtime/boot-root.js +6 -1
- package/dist/core/runtime/broker/client-registry.d.ts +4 -0
- package/dist/core/runtime/broker/client-registry.js +13 -1
- package/dist/core/runtime/broker/extension-abort.d.ts +1 -1
- package/dist/core/runtime/broker/extension-abort.js +4 -2
- package/dist/core/runtime/broker/fault-retry.js +4 -0
- package/dist/core/runtime/broker/frame-dispatch.d.ts +3 -1
- package/dist/core/runtime/broker/frame-dispatch.js +5 -6
- package/dist/core/runtime/broker/read-ops.d.ts +1 -1
- package/dist/core/runtime/broker/read-ops.js +2 -2
- package/dist/core/runtime/broker/rebind.d.ts +1 -0
- package/dist/core/runtime/broker/rebind.js +6 -1
- package/dist/core/runtime/broker-extension-render.js +19 -0
- package/dist/core/runtime/broker-protocol.d.ts +14 -0
- package/dist/core/runtime/broker.d.ts +1 -1
- package/dist/core/runtime/broker.js +25 -12
- package/dist/core/runtime/busy.d.ts +4 -3
- package/dist/core/runtime/busy.js +4 -3
- package/dist/core/runtime/front-door-env.d.ts +5 -0
- package/dist/core/runtime/front-door-env.js +16 -0
- package/dist/core/runtime/front-door.d.ts +1 -5
- package/dist/core/runtime/front-door.js +2 -5
- package/dist/core/runtime/host.d.ts +1 -1
- package/dist/core/runtime/invocation.d.ts +6 -0
- package/dist/core/runtime/invocation.js +10 -0
- package/dist/core/runtime/kickoff.js +34 -0
- package/dist/core/runtime/launch.d.ts +2 -6
- package/dist/core/runtime/node-read.js +4 -1
- package/dist/core/runtime/nodes.js +2 -0
- package/dist/core/runtime/promote.d.ts +1 -0
- package/dist/core/runtime/promote.js +3 -1
- package/dist/core/runtime/session-cycles.d.ts +6 -0
- package/dist/core/runtime/session-cycles.js +15 -1
- package/dist/core/runtime/session-visibility.d.ts +17 -8
- package/dist/core/runtime/session-visibility.js +19 -10
- package/dist/core/runtime/spawn-env.d.ts +9 -1
- package/dist/core/runtime/spawn-env.js +62 -2
- package/dist/core/runtime/spawn.js +4 -4
- package/dist/core/runtime/stop-guard.d.ts +2 -2
- package/dist/core/runtime/stop-guard.js +2 -2
- package/dist/core/runtime/stop-signals.d.ts +1 -1
- package/dist/core/runtime/stop-signals.js +2 -2
- package/dist/core/runtime/tmux-bindings.js +1 -1
- package/dist/core/session-model/session-state.d.ts +4 -3
- package/dist/core/session-model/session-state.js +14 -1
- package/dist/core/user-settings.d.ts +36 -2
- package/dist/core/user-settings.js +56 -14
- package/dist/core/worktree.d.ts +15 -45
- package/dist/core/worktree.js +59 -261
- package/dist/daemon/api/__tests__/seam/profile-delete.test.d.ts +1 -0
- package/dist/daemon/api/__tests__/seam/profile-delete.test.js +386 -0
- package/dist/daemon/api/handlers/bash-jobs.js +1 -1
- package/dist/daemon/api/handlers/broker-ops.js +9 -5
- package/dist/daemon/api/handlers/feedback-comments.d.ts +2 -0
- package/dist/daemon/api/handlers/feedback-comments.js +207 -0
- package/dist/daemon/api/handlers/human.js +24 -20
- package/dist/daemon/api/handlers/inbox.d.ts +10 -0
- package/dist/daemon/api/handlers/inbox.js +48 -109
- package/dist/daemon/api/handlers/profiles.js +11 -29
- package/dist/daemon/api/map.d.ts +6 -1
- package/dist/daemon/api/map.js +23 -0
- package/dist/daemon/api/server.js +3 -1
- package/dist/daemon/companion-retire.d.ts +2 -0
- package/dist/daemon/companion-retire.js +33 -0
- package/dist/daemon/cron-run.js +29 -17
- package/dist/daemon/crtrd.js +1 -1
- package/dist/daemon/fleet.d.ts +22 -10
- package/dist/daemon/fleet.js +43 -24
- package/dist/daemon/human/finish.d.ts +12 -6
- package/dist/daemon/human/finish.js +134 -54
- package/dist/daemon/human/sweep.js +9 -3
- package/dist/daemon/profile-delete.d.ts +7 -0
- package/dist/daemon/profile-delete.js +306 -0
- package/dist/daemon/reconcilers/broker-supervision.js +14 -9
- package/dist/daemon/reconcilers/dormant-inbox.js +11 -6
- package/dist/daemon/reconcilers/live-obligation.d.ts +21 -1
- package/dist/daemon/reconcilers/live-obligation.js +30 -2
- package/dist/daemon/reconcilers/storage-maintenance.d.ts +5 -0
- package/dist/daemon/reconcilers/storage-maintenance.js +28 -1
- package/dist/daemon/review/deliver.js +3 -3
- package/dist/daemon/review/finish.d.ts +0 -2
- package/dist/daemon/review/finish.js +1 -29
- package/dist/daemon/review/sweep.js +12 -1
- package/dist/pi-extensions/__tests__/canvas-bash-valve.test.js +56 -1
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +4 -4
- package/dist/pi-extensions/canvas-bash-valve.d.ts +9 -1
- package/dist/pi-extensions/canvas-bash-valve.js +31 -13
- package/dist/pi-extensions/canvas-inbox-watcher.js +1 -1
- package/dist/pi-extensions/canvas-review-boundary.d.ts +4 -0
- package/dist/pi-extensions/canvas-review-boundary.js +18 -9
- package/dist/pi-extensions/canvas-stophook.js +4 -4
- package/dist/shared/generated-context.js +2 -2
- package/dist/types.d.ts +61 -5
- package/dist/types.js +8 -2
- package/package.json +4 -4
- package/runtime.lock.json +5 -33
- package/scripts/install-runtime.mjs +19 -24
- package/dist/builtin-memory/init.md +0 -38
- package/dist/builtin-memory/plan.md +0 -19
- package/dist/builtin-memory/spec.md +0 -19
- package/dist/clients/attach/render/html-markdown.d.ts +0 -13
- package/dist/clients/attach/render/html-markdown.js +0 -206
- package/dist/commands/human/doc.js +0 -144
- package/dist/commands/human/inbox.d.ts +0 -2
- package/dist/core/human/__tests__/page-render.test.js +0 -49
- package/dist/core/human/markdown-html.d.ts +0 -8
- package/dist/core/human/markdown-html.js +0 -581
- package/dist/core/human/page-render.d.ts +0 -3
- package/dist/core/human/page-render.js +0 -131
- package/dist/pages/bundle.css +0 -1
- package/dist/pages/bundle.js +0 -1347
- package/dist/pages/comments.d.ts +0 -43
- package/dist/pages/comments.js +0 -125
- package/dist/pages/elements/cards.d.ts +0 -17
- package/dist/pages/elements/cards.js +0 -802
- package/dist/pages/elements/chart.js +0 -648
- package/dist/pages/elements/options.d.ts +0 -21
- package/dist/pages/elements/options.js +0 -629
- package/dist/pages/elements/pages.d.ts +0 -24
- package/dist/pages/elements/pages.js +0 -355
- package/dist/pages/elements/slot.d.ts +0 -21
- package/dist/pages/elements/slot.js +0 -105
- package/dist/pages/elements/table.d.ts +0 -19
- package/dist/pages/elements/table.js +0 -935
- package/dist/pages/elements/text.d.ts +0 -25
- package/dist/pages/elements/text.js +0 -968
- package/dist/pages/entry.d.ts +0 -8
- package/dist/pages/entry.js +0 -8
- package/dist/pages/host.d.ts +0 -158
- package/dist/pages/host.js +0 -182
- package/dist/pages/register.d.ts +0 -2
- package/dist/pages/register.js +0 -3
- package/dist/pages/slot-config.d.ts +0 -36
- package/dist/pages/slot-config.js +0 -50
- package/dist/pages/types.d.ts +0 -98
- package/dist/pages/types.js +0 -23
- /package/dist/{core/human/__tests__/page-render.test.d.ts → commands/__tests__/node-message.test.d.ts} +0 -0
- /package/dist/{pages/elements/chart.d.ts → core/__tests__/plugin-page-components.test.d.ts} +0 -0
|
@@ -1,629 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `<crtr-options>` — the agent asks a multiple-choice question.
|
|
3
|
-
*
|
|
4
|
-
* A port of the interaction kit's `agent-options` (Northlight
|
|
5
|
-
* `apps/gateway/components/agent-ui/agent-options.tsx` @ `3705fe9e2`) onto a dependency-free
|
|
6
|
-
* custom element, keeping its two load-bearing decisions:
|
|
7
|
-
*
|
|
8
|
-
* - **The control's silhouette tells the truth about how many answers are wanted.**
|
|
9
|
-
* Single-select is a radio group, multi-select is checkbox rows, so the element never has
|
|
10
|
-
* to say "pick one" in prose.
|
|
11
|
-
* - **"This one, but…" is a first-class answer.** Every option carries the comment
|
|
12
|
-
* affordance in place, rather than making the user squeeze a caveat into the freetext box
|
|
13
|
-
* at the bottom. The affordance is quiet until you reach for it — it reveals on hover, on
|
|
14
|
-
* keyboard focus, and on the `j`/`k` cursor — and stays lit once a comment exists, which
|
|
15
|
-
* is the whole "there is a note here" signal at a glance.
|
|
16
|
-
*
|
|
17
|
-
* Selection is kept in the agent's authored order, never click order, so the response reads
|
|
18
|
-
* the way the question did. `freetext` is absent rather than empty: the response says whether
|
|
19
|
-
* the user wrote something, not whether the box existed.
|
|
20
|
-
*/
|
|
21
|
-
import { adoptComments, setComment, findComment } from '../comments.js';
|
|
22
|
-
import { respond, savedIds, savedResponse } from '../host.js';
|
|
23
|
-
import { registerElement } from '../register.js';
|
|
24
|
-
import { readSlotConfig, readSlotId } from '../slot-config.js';
|
|
25
|
-
const BUBBLE_PLUS = '<svg viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path d="M2.5 3.5h11v7h-6l-3 2.5v-2.5h-2z"/><path d="M8 5.5v3M6.5 7h3"/></svg>';
|
|
26
|
-
const BUBBLE_TEXT = '<svg viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path d="M2.5 3.5h11v7h-6l-3 2.5v-2.5h-2z"/><path d="M5 6.25h6M5 8.25h4"/></svg>';
|
|
27
|
-
const STYLE = `
|
|
28
|
-
:host {
|
|
29
|
-
display: block;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
[part="container"] {
|
|
33
|
-
display: grid;
|
|
34
|
-
gap: var(--crtr-space-3);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
[part="list"] {
|
|
38
|
-
display: grid;
|
|
39
|
-
gap: var(--crtr-space-2);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
[part="option"] {
|
|
43
|
-
position: relative;
|
|
44
|
-
display: flex;
|
|
45
|
-
align-items: flex-start;
|
|
46
|
-
gap: var(--crtr-space-3);
|
|
47
|
-
padding: var(--crtr-space-2) var(--crtr-space-3);
|
|
48
|
-
background: var(--crtr-color-surface);
|
|
49
|
-
border: 1px solid var(--crtr-color-border);
|
|
50
|
-
border-radius: var(--crtr-radius-medium);
|
|
51
|
-
transition: background-color 120ms ease, border-color 120ms ease;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
[part="option"]:hover,
|
|
55
|
-
[part="option"][data-active="true"] {
|
|
56
|
-
background: var(--crtr-color-surface-hover);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
[part="option"][data-selected="true"] {
|
|
60
|
-
border-color: var(--crtr-color-accent);
|
|
61
|
-
background: color-mix(in srgb, var(--crtr-color-accent) 10%, var(--crtr-color-surface));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
[part="option-label"] {
|
|
65
|
-
display: flex;
|
|
66
|
-
flex: 1;
|
|
67
|
-
align-items: flex-start;
|
|
68
|
-
gap: var(--crtr-space-3);
|
|
69
|
-
cursor: pointer;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
[part="option-control"] {
|
|
73
|
-
margin: 0;
|
|
74
|
-
margin-top: 0.15em;
|
|
75
|
-
accent-color: var(--crtr-color-accent);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
[part="option-control"]:focus-visible {
|
|
79
|
-
outline: 2px solid var(--crtr-color-focus);
|
|
80
|
-
outline-offset: 2px;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
[part="option-text"] {
|
|
84
|
-
display: grid;
|
|
85
|
-
gap: var(--crtr-space-1);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
[part="option-title"] {
|
|
89
|
-
font-weight: 500;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
[part="option-description"] {
|
|
93
|
-
color: var(--crtr-color-text-muted);
|
|
94
|
-
font-size: 0.9em;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
[part="comment"] {
|
|
98
|
-
display: inline-flex;
|
|
99
|
-
align-items: center;
|
|
100
|
-
justify-content: center;
|
|
101
|
-
flex: none;
|
|
102
|
-
width: 1.5rem;
|
|
103
|
-
height: 1.5rem;
|
|
104
|
-
padding: 0;
|
|
105
|
-
color: var(--crtr-color-text-muted);
|
|
106
|
-
background: transparent;
|
|
107
|
-
border: 1px solid transparent;
|
|
108
|
-
border-radius: var(--crtr-radius-small);
|
|
109
|
-
opacity: 0;
|
|
110
|
-
cursor: pointer;
|
|
111
|
-
transition: opacity 120ms ease, color 120ms ease, background-color 120ms ease;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
[part="option"]:hover [part="comment"],
|
|
115
|
-
[part="option"][data-active="true"] [part="comment"],
|
|
116
|
-
[part="option"]:focus-within [part="comment"],
|
|
117
|
-
[part="comment"][data-state="commented"],
|
|
118
|
-
[part="comment"][aria-expanded="true"] {
|
|
119
|
-
opacity: 1;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
[part="comment"]:hover {
|
|
123
|
-
background: var(--crtr-color-surface-hover);
|
|
124
|
-
border-color: var(--crtr-color-border);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
[part="comment"]:focus-visible {
|
|
128
|
-
outline: 2px solid var(--crtr-color-focus);
|
|
129
|
-
outline-offset: 1px;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
[part="comment"][data-state="commented"] {
|
|
133
|
-
color: var(--crtr-color-accent);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
[part="comment"] svg {
|
|
137
|
-
width: 0.9rem;
|
|
138
|
-
height: 0.9rem;
|
|
139
|
-
fill: none;
|
|
140
|
-
stroke: currentColor;
|
|
141
|
-
stroke-width: 1.25;
|
|
142
|
-
stroke-linecap: round;
|
|
143
|
-
stroke-linejoin: round;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
[part="comment-popover"] {
|
|
147
|
-
position: absolute;
|
|
148
|
-
top: calc(100% + var(--crtr-space-1));
|
|
149
|
-
right: 0;
|
|
150
|
-
z-index: 2;
|
|
151
|
-
display: grid;
|
|
152
|
-
gap: var(--crtr-space-2);
|
|
153
|
-
width: min(20rem, 100%);
|
|
154
|
-
padding: var(--crtr-space-3);
|
|
155
|
-
background: var(--crtr-color-background);
|
|
156
|
-
border: 1px solid var(--crtr-color-border);
|
|
157
|
-
border-radius: var(--crtr-radius-medium);
|
|
158
|
-
box-shadow: var(--crtr-shadow);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
[part="comment-popover"][hidden] {
|
|
162
|
-
display: none;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
[part="comment-subject"] {
|
|
166
|
-
margin: 0;
|
|
167
|
-
color: var(--crtr-color-text-muted);
|
|
168
|
-
font-family: var(--crtr-font-mono);
|
|
169
|
-
font-size: 0.75em;
|
|
170
|
-
letter-spacing: 0.06em;
|
|
171
|
-
text-transform: uppercase;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
[part="comment-input"],
|
|
175
|
-
[part="freetext-input"] {
|
|
176
|
-
width: 100%;
|
|
177
|
-
box-sizing: border-box;
|
|
178
|
-
padding: var(--crtr-space-2);
|
|
179
|
-
color: var(--crtr-color-text);
|
|
180
|
-
font: inherit;
|
|
181
|
-
background: var(--crtr-color-surface);
|
|
182
|
-
border: 1px solid var(--crtr-color-border);
|
|
183
|
-
border-radius: var(--crtr-radius-small);
|
|
184
|
-
resize: vertical;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
[part="comment-input"] {
|
|
188
|
-
min-height: 4.5rem;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
[part="freetext-input"] {
|
|
192
|
-
min-height: 4rem;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
[part="comment-input"]:focus-visible,
|
|
196
|
-
[part="freetext-input"]:focus-visible {
|
|
197
|
-
outline: 2px solid var(--crtr-color-focus);
|
|
198
|
-
outline-offset: -1px;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
[part="comment-actions"] {
|
|
202
|
-
display: flex;
|
|
203
|
-
align-items: center;
|
|
204
|
-
justify-content: space-between;
|
|
205
|
-
gap: var(--crtr-space-2);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
[part="comment-hint"] {
|
|
209
|
-
color: var(--crtr-color-text-muted);
|
|
210
|
-
font-family: var(--crtr-font-mono);
|
|
211
|
-
font-size: 0.75em;
|
|
212
|
-
letter-spacing: 0.06em;
|
|
213
|
-
text-transform: uppercase;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
[part="comment-cancel"],
|
|
217
|
-
[part="comment-save"] {
|
|
218
|
-
padding: var(--crtr-space-1) var(--crtr-space-2);
|
|
219
|
-
font: inherit;
|
|
220
|
-
font-size: 0.9em;
|
|
221
|
-
border-radius: var(--crtr-radius-small);
|
|
222
|
-
cursor: pointer;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
[part="comment-cancel"] {
|
|
226
|
-
color: var(--crtr-color-text-muted);
|
|
227
|
-
background: transparent;
|
|
228
|
-
border: 1px solid transparent;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
[part="comment-cancel"]:hover {
|
|
232
|
-
color: var(--crtr-color-text);
|
|
233
|
-
background: var(--crtr-color-surface-hover);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
[part="comment-save"] {
|
|
237
|
-
color: var(--crtr-color-accent-contrast);
|
|
238
|
-
background: var(--crtr-color-accent);
|
|
239
|
-
border: 1px solid var(--crtr-color-accent);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
[part="comment-cancel"]:focus-visible,
|
|
243
|
-
[part="comment-save"]:focus-visible {
|
|
244
|
-
outline: 2px solid var(--crtr-color-focus);
|
|
245
|
-
outline-offset: 1px;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
[part="freetext"] {
|
|
249
|
-
display: grid;
|
|
250
|
-
gap: var(--crtr-space-1);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
[part="freetext-label"] {
|
|
254
|
-
color: var(--crtr-color-text-muted);
|
|
255
|
-
font-size: 0.85em;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
[part="unavailable"] {
|
|
259
|
-
padding: var(--crtr-space-3);
|
|
260
|
-
color: var(--crtr-color-text-muted);
|
|
261
|
-
background: var(--crtr-color-surface);
|
|
262
|
-
border: 1px solid var(--crtr-color-border);
|
|
263
|
-
border-radius: var(--crtr-radius-medium);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
[part="reason"] {
|
|
267
|
-
color: var(--crtr-color-text);
|
|
268
|
-
font-family: var(--crtr-font-mono);
|
|
269
|
-
}
|
|
270
|
-
`;
|
|
271
|
-
let groupCounter = 0;
|
|
272
|
-
/**
|
|
273
|
-
* Whether a key belongs to something being written in rather than to the list grammar.
|
|
274
|
-
* The option controls are real radios and checkboxes, so this deliberately admits them —
|
|
275
|
-
* only prose entry (the comment box, the freetext box) swallows `j`/`k`/`x`/`c`.
|
|
276
|
-
*/
|
|
277
|
-
function isTextEntry(target) {
|
|
278
|
-
if (target instanceof HTMLTextAreaElement)
|
|
279
|
-
return true;
|
|
280
|
-
if (target instanceof HTMLElement && target.isContentEditable)
|
|
281
|
-
return true;
|
|
282
|
-
if (!(target instanceof HTMLInputElement))
|
|
283
|
-
return false;
|
|
284
|
-
return target.type !== 'checkbox' && target.type !== 'radio';
|
|
285
|
-
}
|
|
286
|
-
class CrtrOptionsElement extends HTMLElement {
|
|
287
|
-
#shadow;
|
|
288
|
-
#config;
|
|
289
|
-
#slotId;
|
|
290
|
-
#rows = new Map();
|
|
291
|
-
#order = [];
|
|
292
|
-
#selected = [];
|
|
293
|
-
#comments = [];
|
|
294
|
-
#freetext;
|
|
295
|
-
#activeId;
|
|
296
|
-
#commentingId;
|
|
297
|
-
#built = false;
|
|
298
|
-
constructor() {
|
|
299
|
-
super();
|
|
300
|
-
this.#shadow = this.attachShadow({ mode: 'open' });
|
|
301
|
-
}
|
|
302
|
-
connectedCallback() {
|
|
303
|
-
if (this.#built)
|
|
304
|
-
return;
|
|
305
|
-
this.#built = true;
|
|
306
|
-
this.#slotId = readSlotId(this);
|
|
307
|
-
const read = readSlotConfig(this);
|
|
308
|
-
if (read.status !== 'ok') {
|
|
309
|
-
this.#renderUnavailable(read.reason);
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
const config = read.config;
|
|
313
|
-
if (!Array.isArray(config.options) || config.options.length === 0) {
|
|
314
|
-
this.#renderUnavailable('options config lists no options');
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
this.#config = config;
|
|
318
|
-
this.#order = config.options.map((option) => option.id);
|
|
319
|
-
this.#hydrate(config);
|
|
320
|
-
this.#render(config);
|
|
321
|
-
this.#sync();
|
|
322
|
-
}
|
|
323
|
-
disconnectedCallback() {
|
|
324
|
-
document.removeEventListener('pointerdown', this.#onDocumentPointerDown, true);
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Seed local state from the answer the host is already holding, so a reopened page shows
|
|
328
|
-
* what it would submit. Ids that no longer name an option are dropped, and single-select
|
|
329
|
-
* keeps only the first, so a stale saved answer cannot put the control in a state the user
|
|
330
|
-
* could not have clicked into.
|
|
331
|
-
*/
|
|
332
|
-
#hydrate(config) {
|
|
333
|
-
if (this.#slotId === undefined)
|
|
334
|
-
return;
|
|
335
|
-
const saved = savedResponse(this.#slotId);
|
|
336
|
-
if (saved === undefined)
|
|
337
|
-
return;
|
|
338
|
-
const selected = savedIds(saved.selectedOptionIds);
|
|
339
|
-
this.#selected = this.#order.filter((id) => selected.includes(id));
|
|
340
|
-
if (config.mode === 'single')
|
|
341
|
-
this.#selected = this.#selected.slice(0, 1);
|
|
342
|
-
this.#comments = adoptComments(saved.comments).filter((comment) => comment.anchor.kind !== 'option' || this.#order.includes(comment.anchor.optionId));
|
|
343
|
-
if (config.allowFreetext === true && typeof saved.freetext === 'string' && saved.freetext !== '') {
|
|
344
|
-
this.#freetext = saved.freetext;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
/** The named state for a config this element cannot render — never a thrown error. */
|
|
348
|
-
#renderUnavailable(reason) {
|
|
349
|
-
const style = document.createElement('style');
|
|
350
|
-
style.textContent = STYLE;
|
|
351
|
-
const container = document.createElement('div');
|
|
352
|
-
container.part.add('unavailable');
|
|
353
|
-
container.setAttribute('role', 'status');
|
|
354
|
-
const detail = document.createElement('span');
|
|
355
|
-
detail.part.add('reason');
|
|
356
|
-
detail.textContent = reason;
|
|
357
|
-
container.append('Options unavailable: ', detail);
|
|
358
|
-
this.#shadow.replaceChildren(style, container);
|
|
359
|
-
}
|
|
360
|
-
#render(config) {
|
|
361
|
-
const style = document.createElement('style');
|
|
362
|
-
style.textContent = STYLE;
|
|
363
|
-
const container = document.createElement('div');
|
|
364
|
-
container.part.add('container');
|
|
365
|
-
const list = document.createElement('div');
|
|
366
|
-
list.part.add('list');
|
|
367
|
-
if (config.mode === 'single')
|
|
368
|
-
list.setAttribute('role', 'radiogroup');
|
|
369
|
-
groupCounter += 1;
|
|
370
|
-
const group = `crtr-options-${groupCounter}`;
|
|
371
|
-
for (const option of config.options)
|
|
372
|
-
list.append(this.#renderOption(config, option, group));
|
|
373
|
-
container.append(list);
|
|
374
|
-
if (config.allowFreetext === true)
|
|
375
|
-
container.append(this.#renderFreetext(config));
|
|
376
|
-
// The grammar lives on the container, so it reaches every row and never escapes the
|
|
377
|
-
// shadow root: `j`/`k` move the cursor, `x` toggles it, `c` comments on it.
|
|
378
|
-
container.addEventListener('keydown', (event) => this.#onKeydown(event));
|
|
379
|
-
this.#shadow.replaceChildren(style, container);
|
|
380
|
-
}
|
|
381
|
-
#renderOption(config, option, group) {
|
|
382
|
-
const root = document.createElement('div');
|
|
383
|
-
root.part.add('option');
|
|
384
|
-
root.dataset.optionId = option.id;
|
|
385
|
-
const label = document.createElement('label');
|
|
386
|
-
label.part.add('option-label');
|
|
387
|
-
const control = document.createElement('input');
|
|
388
|
-
control.part.add('option-control');
|
|
389
|
-
control.type = config.mode === 'single' ? 'radio' : 'checkbox';
|
|
390
|
-
control.name = group;
|
|
391
|
-
control.value = option.id;
|
|
392
|
-
control.addEventListener('change', () => this.#toggle(option.id, control.checked));
|
|
393
|
-
control.addEventListener('focus', () => this.#setActive(option.id));
|
|
394
|
-
const text = document.createElement('span');
|
|
395
|
-
text.part.add('option-text');
|
|
396
|
-
const title = document.createElement('span');
|
|
397
|
-
title.part.add('option-title');
|
|
398
|
-
title.textContent = option.label;
|
|
399
|
-
text.append(title);
|
|
400
|
-
if (option.description !== undefined && option.description !== '') {
|
|
401
|
-
const description = document.createElement('span');
|
|
402
|
-
description.part.add('option-description');
|
|
403
|
-
description.textContent = option.description;
|
|
404
|
-
text.append(description);
|
|
405
|
-
}
|
|
406
|
-
label.append(control, text);
|
|
407
|
-
const comment = document.createElement('button');
|
|
408
|
-
comment.part.add('comment');
|
|
409
|
-
comment.type = 'button';
|
|
410
|
-
comment.setAttribute('aria-haspopup', 'dialog');
|
|
411
|
-
comment.addEventListener('click', () => {
|
|
412
|
-
if (this.#commentingId === option.id)
|
|
413
|
-
this.#closeComment();
|
|
414
|
-
else
|
|
415
|
-
this.#openComment(option.id);
|
|
416
|
-
});
|
|
417
|
-
const popover = document.createElement('div');
|
|
418
|
-
popover.part.add('comment-popover');
|
|
419
|
-
popover.setAttribute('role', 'dialog');
|
|
420
|
-
popover.hidden = true;
|
|
421
|
-
const subject = document.createElement('p');
|
|
422
|
-
subject.part.add('comment-subject');
|
|
423
|
-
subject.textContent = `Comment on ${option.label}`;
|
|
424
|
-
popover.setAttribute('aria-label', subject.textContent);
|
|
425
|
-
const input = document.createElement('textarea');
|
|
426
|
-
input.part.add('comment-input');
|
|
427
|
-
input.placeholder = 'What should the agent know about this?';
|
|
428
|
-
input.addEventListener('keydown', (event) => {
|
|
429
|
-
if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
|
|
430
|
-
event.preventDefault();
|
|
431
|
-
this.#commitComment(option.id);
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
if (event.key === 'Escape') {
|
|
435
|
-
event.preventDefault();
|
|
436
|
-
this.#closeComment();
|
|
437
|
-
}
|
|
438
|
-
});
|
|
439
|
-
const actions = document.createElement('div');
|
|
440
|
-
actions.part.add('comment-actions');
|
|
441
|
-
const hint = document.createElement('span');
|
|
442
|
-
hint.part.add('comment-hint');
|
|
443
|
-
const buttons = document.createElement('span');
|
|
444
|
-
const cancel = document.createElement('button');
|
|
445
|
-
cancel.part.add('comment-cancel');
|
|
446
|
-
cancel.type = 'button';
|
|
447
|
-
cancel.textContent = 'Cancel';
|
|
448
|
-
cancel.addEventListener('click', () => this.#closeComment());
|
|
449
|
-
const save = document.createElement('button');
|
|
450
|
-
save.part.add('comment-save');
|
|
451
|
-
save.type = 'button';
|
|
452
|
-
save.textContent = 'Save';
|
|
453
|
-
save.addEventListener('click', () => this.#commitComment(option.id));
|
|
454
|
-
buttons.append(cancel, save);
|
|
455
|
-
actions.append(hint, buttons);
|
|
456
|
-
popover.append(subject, input, actions);
|
|
457
|
-
root.append(label, comment, popover);
|
|
458
|
-
root.addEventListener('mouseenter', () => this.#setActive(option.id));
|
|
459
|
-
root.addEventListener('mouseleave', () => this.#clearActive(option.id));
|
|
460
|
-
this.#rows.set(option.id, { id: option.id, label: option.label, root, control, comment, popover, input, hint });
|
|
461
|
-
return root;
|
|
462
|
-
}
|
|
463
|
-
#renderFreetext(config) {
|
|
464
|
-
const wrapper = document.createElement('label');
|
|
465
|
-
wrapper.part.add('freetext');
|
|
466
|
-
const label = document.createElement('span');
|
|
467
|
-
label.part.add('freetext-label');
|
|
468
|
-
label.textContent = config.freetextLabel ?? 'Something else';
|
|
469
|
-
const input = document.createElement('textarea');
|
|
470
|
-
input.part.add('freetext-input');
|
|
471
|
-
input.placeholder = config.freetextPlaceholder ?? 'Answer in your own words…';
|
|
472
|
-
// Hydration runs before the render, so the saved words are already in local state.
|
|
473
|
-
if (this.#freetext !== undefined)
|
|
474
|
-
input.value = this.#freetext;
|
|
475
|
-
input.addEventListener('input', () => {
|
|
476
|
-
// Absent rather than empty — see the note on `freetext` in the response type.
|
|
477
|
-
this.#freetext = input.value === '' ? undefined : input.value;
|
|
478
|
-
this.#emit();
|
|
479
|
-
});
|
|
480
|
-
wrapper.append(label, input);
|
|
481
|
-
return wrapper;
|
|
482
|
-
}
|
|
483
|
-
#onKeydown(event) {
|
|
484
|
-
if (event.metaKey || event.ctrlKey || event.altKey)
|
|
485
|
-
return;
|
|
486
|
-
if (isTextEntry(event.target))
|
|
487
|
-
return;
|
|
488
|
-
const key = event.key.toLowerCase();
|
|
489
|
-
const current = this.#activeId ?? this.#selected.at(-1) ?? this.#order[0];
|
|
490
|
-
if (current === undefined)
|
|
491
|
-
return;
|
|
492
|
-
if (key === 'j' || key === 'k') {
|
|
493
|
-
event.preventDefault();
|
|
494
|
-
const index = Math.max(0, this.#order.indexOf(current));
|
|
495
|
-
const step = key === 'j' ? 1 : -1;
|
|
496
|
-
const next = this.#order[(index + step + this.#order.length) % this.#order.length];
|
|
497
|
-
this.#setActive(next);
|
|
498
|
-
this.#rows.get(next)?.control.focus();
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
if (key === 'x') {
|
|
502
|
-
event.preventDefault();
|
|
503
|
-
const selected = this.#selected.includes(current);
|
|
504
|
-
this.#toggle(current, this.#config?.mode === 'single' ? true : !selected);
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
if (key === 'c') {
|
|
508
|
-
event.preventDefault();
|
|
509
|
-
this.#openComment(current);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
#setActive(optionId) {
|
|
513
|
-
if (this.#activeId === optionId)
|
|
514
|
-
return;
|
|
515
|
-
this.#activeId = optionId;
|
|
516
|
-
this.#sync();
|
|
517
|
-
}
|
|
518
|
-
#clearActive(optionId) {
|
|
519
|
-
if (this.#activeId !== optionId)
|
|
520
|
-
return;
|
|
521
|
-
this.#activeId = undefined;
|
|
522
|
-
this.#sync();
|
|
523
|
-
}
|
|
524
|
-
#toggle(optionId, selected) {
|
|
525
|
-
if (this.#config === undefined)
|
|
526
|
-
return;
|
|
527
|
-
if (this.#config.mode === 'single') {
|
|
528
|
-
this.#selected = [optionId];
|
|
529
|
-
}
|
|
530
|
-
else {
|
|
531
|
-
const next = selected
|
|
532
|
-
? [...this.#selected, optionId]
|
|
533
|
-
: this.#selected.filter((id) => id !== optionId);
|
|
534
|
-
// Kept in the agent's authored order, so the response reads the way the question did
|
|
535
|
-
// rather than in click order.
|
|
536
|
-
this.#selected = this.#order.filter((id) => next.includes(id));
|
|
537
|
-
}
|
|
538
|
-
this.#setActive(optionId);
|
|
539
|
-
this.#sync();
|
|
540
|
-
this.#emit();
|
|
541
|
-
}
|
|
542
|
-
/**
|
|
543
|
-
* A press anywhere but the open box and its trigger closes the box without committing,
|
|
544
|
-
* matching the kit's popover. It listens on the document rather than on this element,
|
|
545
|
-
* because "anywhere else" includes the rest of the page: `pointerdown` is composed, so the
|
|
546
|
-
* path still names the shadow nodes an inside press went through.
|
|
547
|
-
*/
|
|
548
|
-
#onDocumentPointerDown = (event) => {
|
|
549
|
-
if (this.#commentingId === undefined)
|
|
550
|
-
return;
|
|
551
|
-
const row = this.#rows.get(this.#commentingId);
|
|
552
|
-
if (row === undefined)
|
|
553
|
-
return;
|
|
554
|
-
const path = event.composedPath();
|
|
555
|
-
if (path.includes(row.popover) || path.includes(row.comment))
|
|
556
|
-
return;
|
|
557
|
-
// No focus restore: the press is on its way somewhere else, and pulling focus back to
|
|
558
|
-
// the trigger would fight it.
|
|
559
|
-
this.#closeComment(false);
|
|
560
|
-
};
|
|
561
|
-
#openComment(optionId) {
|
|
562
|
-
const row = this.#rows.get(optionId);
|
|
563
|
-
if (row === undefined)
|
|
564
|
-
return;
|
|
565
|
-
if (this.#commentingId === undefined) {
|
|
566
|
-
document.addEventListener('pointerdown', this.#onDocumentPointerDown, true);
|
|
567
|
-
}
|
|
568
|
-
this.#commentingId = optionId;
|
|
569
|
-
this.#activeId = optionId;
|
|
570
|
-
// Seeded from the saved comment every time it opens.
|
|
571
|
-
row.input.value = findComment(this.#comments, anchorFor(optionId))?.text ?? '';
|
|
572
|
-
this.#sync();
|
|
573
|
-
row.input.focus();
|
|
574
|
-
}
|
|
575
|
-
#closeComment(restoreFocus = true) {
|
|
576
|
-
if (this.#commentingId === undefined)
|
|
577
|
-
return;
|
|
578
|
-
const row = this.#rows.get(this.#commentingId);
|
|
579
|
-
this.#commentingId = undefined;
|
|
580
|
-
document.removeEventListener('pointerdown', this.#onDocumentPointerDown, true);
|
|
581
|
-
this.#sync();
|
|
582
|
-
if (restoreFocus)
|
|
583
|
-
row?.comment.focus();
|
|
584
|
-
}
|
|
585
|
-
#commitComment(optionId) {
|
|
586
|
-
const row = this.#rows.get(optionId);
|
|
587
|
-
if (row === undefined)
|
|
588
|
-
return;
|
|
589
|
-
// Clearing the box deletes the comment — `setComment` treats empty text as removal, so
|
|
590
|
-
// there is no separate destructive control.
|
|
591
|
-
this.#comments = setComment(this.#comments, anchorFor(optionId), row.input.value);
|
|
592
|
-
this.#closeComment();
|
|
593
|
-
this.#emit();
|
|
594
|
-
}
|
|
595
|
-
/** Paint every row from local state; local state is authoritative for rendering. */
|
|
596
|
-
#sync() {
|
|
597
|
-
for (const row of this.#rows.values()) {
|
|
598
|
-
const selected = this.#selected.includes(row.id);
|
|
599
|
-
row.control.checked = selected;
|
|
600
|
-
row.root.dataset.selected = String(selected);
|
|
601
|
-
row.root.dataset.active = String(this.#activeId === row.id);
|
|
602
|
-
const comment = findComment(this.#comments, anchorFor(row.id));
|
|
603
|
-
row.comment.dataset.state = comment === undefined ? 'empty' : 'commented';
|
|
604
|
-
row.comment.innerHTML = comment === undefined ? BUBBLE_PLUS : BUBBLE_TEXT;
|
|
605
|
-
row.comment.setAttribute('aria-label', comment === undefined ? `Comment on ${row.label}` : `Edit comment on ${row.label}`);
|
|
606
|
-
const open = this.#commentingId === row.id;
|
|
607
|
-
row.comment.setAttribute('aria-expanded', String(open));
|
|
608
|
-
row.popover.hidden = !open;
|
|
609
|
-
row.hint.textContent = comment === undefined ? '\u2318\u21b5 to save' : 'empty to delete';
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
/** The complete current response, on every user change — never a delta. */
|
|
613
|
-
#emit() {
|
|
614
|
-
if (this.#slotId === undefined)
|
|
615
|
-
return;
|
|
616
|
-
const response = {
|
|
617
|
-
selectedOptionIds: [...this.#selected],
|
|
618
|
-
comments: this.#comments.map((comment) => ({ ...comment })),
|
|
619
|
-
};
|
|
620
|
-
if (this.#config?.allowFreetext === true && this.#freetext !== undefined) {
|
|
621
|
-
response.freetext = this.#freetext;
|
|
622
|
-
}
|
|
623
|
-
respond(this.#slotId, response);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
function anchorFor(optionId) {
|
|
627
|
-
return { kind: 'option', optionId };
|
|
628
|
-
}
|
|
629
|
-
registerElement('crtr-options', CrtrOptionsElement);
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `<crtr-pages>` / `<crtr-page>` — the page's own pager, and the only thing that submits.
|
|
3
|
-
*
|
|
4
|
-
* A response-bearing page wraps its slots in exactly one `<crtr-pages>` containing one or
|
|
5
|
-
* more `<crtr-page>` steps. This element shows one step at a time, carries the step
|
|
6
|
-
* progress and the forward/back affordances, and renders the submit affordance on the
|
|
7
|
-
* final step — the in-document chrome, and nothing else. Dismiss and save are host chrome
|
|
8
|
-
* (a frame around the page, not markup inside it) and never appear here.
|
|
9
|
-
*
|
|
10
|
-
* **Flipping steps is the same motion as flipping tickets, one level down.** The inbox is
|
|
11
|
-
* a pager over pending pages; this is a pager over the steps of one page. So the grammar is
|
|
12
|
-
* deliberately the narrow one both levels share — go to the next unit of work, go back to
|
|
13
|
-
* the previous one, submit — with no third way to move. `n` / `p` are the keys, matching the
|
|
14
|
-
* terminal page panel's within-page motion (`src/clients/inbox/tui/input.ts`), because the
|
|
15
|
-
* kit's `j` / `k` / `x` / `c` belong to the items *inside* a slot and a pager that stole them
|
|
16
|
-
* would make one keystroke mean two levels at once. Submit is `⌘`/`Ctrl` + `Enter` — the
|
|
17
|
-
* inbox's own submit chord, unchanged, because submitting an answer is literally the same
|
|
18
|
-
* act at both levels. The step dots are an indicator, not a control: a page is answered by
|
|
19
|
-
* walking it, not by jumping around it.
|
|
20
|
-
*
|
|
21
|
-
* A single-step page renders no pager chrome at all — no progress, no forward/back — just
|
|
22
|
-
* the submit affordance, because there is nothing to flip.
|
|
23
|
-
*/
|
|
24
|
-
export {};
|