@agentwhy/cli 0.0.0-dev
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/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +181 -0
- package/THIRD_PARTY_NOTICES.md +31 -0
- package/dist/adapter/claude-code/contract/assumptions.js +208 -0
- package/dist/adapter/claude-code/contract/denials.js +15 -0
- package/dist/adapter/claude-code/contract/entry-points.js +15 -0
- package/dist/adapter/claude-code/contract/fields.js +73 -0
- package/dist/adapter/claude-code/contract/hooks.js +96 -0
- package/dist/adapter/claude-code/contract/identifiers.js +15 -0
- package/dist/adapter/claude-code/contract/layout.js +29 -0
- package/dist/adapter/claude-code/contract/line-types.js +26 -0
- package/dist/adapter/claude-code/contract/projects.js +23 -0
- package/dist/adapter/claude-code/contract/recognition.js +14 -0
- package/dist/adapter/claude-code/contract/session-title.js +20 -0
- package/dist/adapter/claude-code/contract/settings.js +27 -0
- package/dist/adapter/claude-code/contract/task-notifications.js +33 -0
- package/dist/adapter/claude-code/contract/tools.js +37 -0
- package/dist/adapter/claude-code/contract/version.js +51 -0
- package/dist/adapter/claude-code/discovery/claude-code-project-catalogue.js +129 -0
- package/dist/adapter/claude-code/discovery/claude-code-recognition.js +46 -0
- package/dist/adapter/claude-code/discovery/claude-code-session-catalogue.js +100 -0
- package/dist/adapter/claude-code/discovery/claude-code-session-discovery.js +61 -0
- package/dist/adapter/claude-code/discovery/claude-code-session-titles.js +28 -0
- package/dist/adapter/claude-code/discovery/discovered-session.js +1 -0
- package/dist/adapter/claude-code/discovery/session-entries.js +41 -0
- package/dist/adapter/claude-code/discovery/transcript-tail.js +81 -0
- package/dist/adapter/claude-code/events/claude-code-session-source.js +197 -0
- package/dist/adapter/claude-code/events/delegation-index.js +15 -0
- package/dist/adapter/claude-code/events/transcript-scan.js +179 -0
- package/dist/adapter/claude-code/hooks/hook-output.js +42 -0
- package/dist/adapter/claude-code/hooks/pre-tool-use-input.js +23 -0
- package/dist/adapter/claude-code/hooks/stop-input.js +25 -0
- package/dist/adapter/claude-code/hooks/subagent-stop-input.js +26 -0
- package/dist/adapter/claude-code/policy/deny-rules.js +46 -0
- package/dist/adapter/claude-code/probe/claude-code-probe.js +86 -0
- package/dist/adapter/claude-code/probe/collectors/agent-tool-input-key-collector.js +24 -0
- package/dist/adapter/claude-code/probe/collectors/denial-kind-collector.js +20 -0
- package/dist/adapter/claude-code/probe/collectors/denied-call-collector.js +11 -0
- package/dist/adapter/claude-code/probe/collectors/identifier-collector.js +19 -0
- package/dist/adapter/claude-code/probe/collectors/line-collector.js +1 -0
- package/dist/adapter/claude-code/probe/collectors/line-type-collector.js +29 -0
- package/dist/adapter/claude-code/probe/collectors/sidechain-collector.js +20 -0
- package/dist/adapter/claude-code/probe/collectors/task-notification-collector.js +51 -0
- package/dist/adapter/claude-code/probe/collectors/tool-result-reference-collector.js +31 -0
- package/dist/adapter/claude-code/probe/collectors/tool-use-collector.js +24 -0
- package/dist/adapter/claude-code/probe/collectors/tool-version-collector.js +14 -0
- package/dist/adapter/claude-code/probe/collectors/top-level-key-collector.js +14 -0
- package/dist/adapter/claude-code/probe/collectors/unknown-line-type-key-collector.js +24 -0
- package/dist/adapter/claude-code/probe/collectors/working-directory-collector.js +28 -0
- package/dist/adapter/claude-code/probe/doctor-report.js +1 -0
- package/dist/adapter/claude-code/probe/meta-tally.js +42 -0
- package/dist/adapter/claude-code/probe/session-probe.js +1 -0
- package/dist/adapter/claude-code/probe/session-tally.js +41 -0
- package/dist/adapter/claude-code/probe/transcript-line.js +30 -0
- package/dist/adapter/claude-code/probe/transcript-tally.js +74 -0
- package/dist/adapter/claude-code/settings/deny-entries.js +65 -0
- package/dist/adapter/claude-code/settings/hook-entries.js +303 -0
- package/dist/adapter/codex/contract/actions.js +69 -0
- package/dist/adapter/codex/contract/assumptions.js +55 -0
- package/dist/adapter/codex/contract/capabilities.js +24 -0
- package/dist/adapter/codex/contract/delegations.js +30 -0
- package/dist/adapter/codex/contract/deliveries.js +25 -0
- package/dist/adapter/codex/contract/envelope.js +33 -0
- package/dist/adapter/codex/contract/hook-refusals.js +14 -0
- package/dist/adapter/codex/contract/hooks.js +46 -0
- package/dist/adapter/codex/contract/messages.js +47 -0
- package/dist/adapter/codex/contract/permissions.js +27 -0
- package/dist/adapter/codex/contract/reviews.js +13 -0
- package/dist/adapter/codex/contract/session.js +31 -0
- package/dist/adapter/codex/contract/turns.js +23 -0
- package/dist/adapter/codex/contract/version.js +15 -0
- package/dist/adapter/codex/discovery/codex-conversations.js +44 -0
- package/dist/adapter/codex/discovery/codex-project-catalogue.js +49 -0
- package/dist/adapter/codex/discovery/codex-recognition.js +20 -0
- package/dist/adapter/codex/discovery/codex-session-catalogue.js +28 -0
- package/dist/adapter/codex/discovery/codex-session-discovery.js +115 -0
- package/dist/adapter/codex/discovery/codex-session-index.js +21 -0
- package/dist/adapter/codex/discovery/codex-session-titles.js +135 -0
- package/dist/adapter/codex/discovery/session-header.js +86 -0
- package/dist/adapter/codex/discovery/session-owners.js +11 -0
- package/dist/adapter/codex/discovery/session-roots.js +49 -0
- package/dist/adapter/codex/events/action-items.js +138 -0
- package/dist/adapter/codex/events/capability-records.js +26 -0
- package/dist/adapter/codex/events/codex-session-source.js +121 -0
- package/dist/adapter/codex/events/hook-refusals.js +27 -0
- package/dist/adapter/codex/events/rollout-scan.js +614 -0
- package/dist/adapter/codex/events/turn-permissions.js +60 -0
- package/dist/adapter/codex/hooks/pre-tool-use-input.js +24 -0
- package/dist/adapter/codex/hooks/stop-refusals.js +57 -0
- package/dist/adapter/codex/probe/codex-doctor-report.js +1 -0
- package/dist/adapter/codex/probe/codex-probe.js +268 -0
- package/dist/adapter/codex/settings/codex-hooks.js +83 -0
- package/dist/cli/cli-command.js +1 -0
- package/dist/cli/command-router.js +42 -0
- package/dist/cli/commands/check-cli-command.js +128 -0
- package/dist/cli/commands/codex-stop-cli-command.js +27 -0
- package/dist/cli/commands/doctor-arguments.js +32 -0
- package/dist/cli/commands/doctor-cli-command.js +31 -0
- package/dist/cli/commands/doctor-usage.js +14 -0
- package/dist/cli/commands/init-cli-command.js +123 -0
- package/dist/cli/commands/menu-cli-command.js +58 -0
- package/dist/cli/commands/notify-cli-command.js +158 -0
- package/dist/cli/commands/refuse-cli-command.js +79 -0
- package/dist/cli/commands/report-arguments.js +62 -0
- package/dist/cli/commands/report-cli-command.js +36 -0
- package/dist/cli/commands/report-usage.js +41 -0
- package/dist/cli/commands/sessions-cli-command.js +184 -0
- package/dist/cli/commands/start-cli-command.js +83 -0
- package/dist/cli/commands/watch-cli-command.js +142 -0
- package/dist/cli/exit-codes.js +13 -0
- package/dist/cli/project-switches.js +46 -0
- package/dist/cli.js +80 -0
- package/dist/composition-root.js +411 -0
- package/dist/core/access/command-line.js +454 -0
- package/dist/core/access/listing.js +58 -0
- package/dist/core/access/path-shape.js +59 -0
- package/dist/core/access/path-tokens.js +37 -0
- package/dist/core/access/protected-access.js +310 -0
- package/dist/core/access/protected-values.js +102 -0
- package/dist/core/access/recorded-effect.js +67 -0
- package/dist/core/access/returns.js +173 -0
- package/dist/core/access/search-output.js +34 -0
- package/dist/core/access/search-reach.js +268 -0
- package/dist/core/access/uses.js +143 -0
- package/dist/core/access/words-before.js +52 -0
- package/dist/core/agent.js +1 -0
- package/dist/core/capability.js +11 -0
- package/dist/core/combined-project-catalogue.js +36 -0
- package/dist/core/combined-session-catalogue.js +25 -0
- package/dist/core/completeness.js +10 -0
- package/dist/core/context.js +1 -0
- package/dist/core/correlation/correlate.js +329 -0
- package/dist/core/delegation.js +1 -0
- package/dist/core/detector/prompt-signals.js +78 -0
- package/dist/core/entry-point.js +1 -0
- package/dist/core/event.js +1 -0
- package/dist/core/evidence.js +9 -0
- package/dist/core/late-message.js +32 -0
- package/dist/core/message.js +1 -0
- package/dist/core/nearest-project.js +28 -0
- package/dist/core/policy/default-policy.js +32 -0
- package/dist/core/policy/glob.js +56 -0
- package/dist/core/policy/parse-policy.js +70 -0
- package/dist/core/policy/policy.js +20 -0
- package/dist/core/policy/resolve-policy.js +16 -0
- package/dist/core/project-catalogue.js +1 -0
- package/dist/core/project-root.js +133 -0
- package/dist/core/provider-titles.js +10 -0
- package/dist/core/redaction/entropy.js +46 -0
- package/dist/core/redaction/redacted.js +4 -0
- package/dist/core/redaction/redactor.js +339 -0
- package/dist/core/redaction/secret-patterns.js +22 -0
- package/dist/core/redaction/value-shapes.js +81 -0
- package/dist/core/redaction/value-trace.js +110 -0
- package/dist/core/review.js +1 -0
- package/dist/core/session-catalogue.js +19 -0
- package/dist/core/session-filter.js +42 -0
- package/dist/core/session-format.js +27 -0
- package/dist/core/session-model.js +1 -0
- package/dist/core/session-reader.js +22 -0
- package/dist/core/session-records.js +1 -0
- package/dist/core/session-source.js +1 -0
- package/dist/core/session-titles.js +21 -0
- package/dist/core/turn.js +1 -0
- package/dist/doctor/codex-doctor.js +14 -0
- package/dist/doctor/doctor-use-case.js +1 -0
- package/dist/doctor/format-selecting-doctor.js +28 -0
- package/dist/doctor/render/attention/attention-rule.js +1 -0
- package/dist/doctor/render/attention/doctor-attention-rules.js +28 -0
- package/dist/doctor/render/attention/rules/damaged-meta-files-rule.js +8 -0
- package/dist/doctor/render/attention/rules/damaged-transcripts-rule.js +9 -0
- package/dist/doctor/render/attention/rules/incomplete-subagent-pairs-rule.js +6 -0
- package/dist/doctor/render/attention/rules/missing-main-transcript-rule.js +5 -0
- package/dist/doctor/render/attention/rules/missing-spilled-results-rule.js +6 -0
- package/dist/doctor/render/attention/rules/multiple-working-directories-rule.js +15 -0
- package/dist/doctor/render/attention/rules/unknown-denial-kinds-rule.js +5 -0
- package/dist/doctor/render/attention/rules/unknown-line-types-rule.js +6 -0
- package/dist/doctor/render/attention/rules/unrecognised-entries-rule.js +6 -0
- package/dist/doctor/render/attention/rules/unusable-directories-rule.js +12 -0
- package/dist/doctor/render/attention/transcript-scopes.js +6 -0
- package/dist/doctor/render/codex-text-doctor-renderer.js +97 -0
- package/dist/doctor/render/doctor-renderers.js +1 -0
- package/dist/doctor/render/json-doctor-renderer.js +7 -0
- package/dist/doctor/render/output-format.js +1 -0
- package/dist/doctor/render/text-doctor-renderer.js +124 -0
- package/dist/doctor/session-doctor.js +16 -0
- package/dist/infrastructure/clack-asker.js +24 -0
- package/dist/infrastructure/clack-chooser.js +40 -0
- package/dist/infrastructure/clack-multi-chooser.js +31 -0
- package/dist/infrastructure/file-alert-store.js +120 -0
- package/dist/infrastructure/file-checked-store.js +55 -0
- package/dist/infrastructure/file-mark-store.js +67 -0
- package/dist/infrastructure/file-onboarding-store.js +93 -0
- package/dist/infrastructure/folder-window.js +26 -0
- package/dist/infrastructure/node-agentwhy-invocation.js +99 -0
- package/dist/infrastructure/node-browser.js +40 -0
- package/dist/infrastructure/node-file-system.js +111 -0
- package/dist/infrastructure/node-local-server.js +115 -0
- package/dist/infrastructure/node-text-input.js +26 -0
- package/dist/infrastructure/os-notifier.js +21 -0
- package/dist/infrastructure/osascript-folder-chooser.js +53 -0
- package/dist/infrastructure/powershell-folder-chooser.js +78 -0
- package/dist/infrastructure/real-directory.js +18 -0
- package/dist/infrastructure/stream-printer.js +11 -0
- package/dist/infrastructure/terminal-banner.js +19 -0
- package/dist/ports/agentwhy-invocation.js +1 -0
- package/dist/ports/alert-store.js +1 -0
- package/dist/ports/asker.js +1 -0
- package/dist/ports/browser.js +1 -0
- package/dist/ports/checked-store.js +1 -0
- package/dist/ports/chooser.js +1 -0
- package/dist/ports/directory-reader.js +1 -0
- package/dist/ports/file-access-error.js +11 -0
- package/dist/ports/file-reader.js +1 -0
- package/dist/ports/file-tail-reader.js +1 -0
- package/dist/ports/file-writer.js +1 -0
- package/dist/ports/folder-chooser.js +1 -0
- package/dist/ports/local-server.js +1 -0
- package/dist/ports/mark-store.js +10 -0
- package/dist/ports/multi-chooser.js +1 -0
- package/dist/ports/notifier.js +1 -0
- package/dist/ports/onboarding-store.js +1 -0
- package/dist/ports/printer.js +1 -0
- package/dist/ports/text-input.js +1 -0
- package/dist/refuse/command-reach.js +135 -0
- package/dist/refuse/command-refusal.js +88 -0
- package/dist/refuse/render/codex-stop-words.js +21 -0
- package/dist/refuse/render/refusal-words.js +34 -0
- package/dist/report/build-report.js +1037 -0
- package/dist/report/check/actions-digest.js +73 -0
- package/dist/report/check/check-lines.js +83 -0
- package/dist/report/check/mark-request.js +90 -0
- package/dist/report/check/marks.js +84 -0
- package/dist/report/check/render/text-digest-renderer.js +332 -0
- package/dist/report/check/session-actions.js +90 -0
- package/dist/report/check/session-check.js +91 -0
- package/dist/report/choose-policy.js +61 -0
- package/dist/report/flow-reads.js +18 -0
- package/dist/report/private-files/tell-lists.js +84 -0
- package/dist/report/project-rules.js +33 -0
- package/dist/report/render/home-relative.js +18 -0
- package/dist/report/render/html-head.js +26 -0
- package/dist/report/render/html-report-components.js +48 -0
- package/dist/report/render/html-report-styles.js +500 -0
- package/dist/report/render/html-sidebar.js +42 -0
- package/dist/report/render/path-tail.js +14 -0
- package/dist/report/render/report-copy.js +1177 -0
- package/dist/report/render/report-page/advanced-view.js +167 -0
- package/dist/report/render/report-page/file-story.js +114 -0
- package/dist/report/render/report-page/files-view.js +381 -0
- package/dist/report/render/report-page/files.js +77 -0
- package/dist/report/render/report-page/fix-wizard-script.js +269 -0
- package/dist/report/render/report-page/fix-wizard.js +310 -0
- package/dist/report/render/report-page/helpers-view.js +383 -0
- package/dist/report/render/report-page/helpers.js +51 -0
- package/dist/report/render/report-page/item-names.js +63 -0
- package/dist/report/render/report-page/protect-patterns.js +13 -0
- package/dist/report/render/report-page/providers.js +208 -0
- package/dist/report/render/report-page/record-view.js +367 -0
- package/dist/report/render/report-page/report-page-renderer.js +129 -0
- package/dist/report/render/report-page/report-views.js +31 -0
- package/dist/report/render/report-page/story-window.js +356 -0
- package/dist/report/render/report-page/times.js +25 -0
- package/dist/report/render/report-page/to-do-view.js +299 -0
- package/dist/report/render/report-page/to-do.js +152 -0
- package/dist/report/render/report-page.js +1 -0
- package/dist/report/render/session-view.js +250 -0
- package/dist/report/render/text-report-renderer.js +981 -0
- package/dist/report/render/ui/add-file-popup.js +122 -0
- package/dist/report/render/ui/app-sidebar.js +78 -0
- package/dist/report/render/ui/ask-panel.js +80 -0
- package/dist/report/render/ui/avatar.js +19 -0
- package/dist/report/render/ui/backdrop.js +29 -0
- package/dist/report/render/ui/brand-mark.js +4 -0
- package/dist/report/render/ui/button.js +67 -0
- package/dist/report/render/ui/callout.js +20 -0
- package/dist/report/render/ui/chat-example.js +17 -0
- package/dist/report/render/ui/checklist.js +50 -0
- package/dist/report/render/ui/confirm-dialog.js +34 -0
- package/dist/report/render/ui/data-table.js +49 -0
- package/dist/report/render/ui/drawer.js +25 -0
- package/dist/report/render/ui/file-chip.js +37 -0
- package/dist/report/render/ui/fold-line.js +22 -0
- package/dist/report/render/ui/guide-card.js +25 -0
- package/dist/report/render/ui/hero.js +37 -0
- package/dist/report/render/ui/labelled-select.js +10 -0
- package/dist/report/render/ui/live-script.js +222 -0
- package/dist/report/render/ui/local-date.js +58 -0
- package/dist/report/render/ui/mode-icon.js +6 -0
- package/dist/report/render/ui/page-shell.js +101 -0
- package/dist/report/render/ui/pill-tabs.js +47 -0
- package/dist/report/render/ui/popup.js +77 -0
- package/dist/report/render/ui/progress.js +35 -0
- package/dist/report/render/ui/project-list.js +284 -0
- package/dist/report/render/ui/scope-choice.js +19 -0
- package/dist/report/render/ui/stats.js +21 -0
- package/dist/report/render/ui/status-icon.js +30 -0
- package/dist/report/render/ui/status-look.js +21 -0
- package/dist/report/render/ui/switch.js +11 -0
- package/dist/report/render/ui/tag.js +19 -0
- package/dist/report/render/ui/task-list.js +56 -0
- package/dist/report/render/ui/tokens.js +46 -0
- package/dist/report/render/ui/update-notice.js +146 -0
- package/dist/report/render/ui/words/app-words.js +163 -0
- package/dist/report/render/ui/words/conversations-words.js +457 -0
- package/dist/report/render/ui/words/month-words.js +59 -0
- package/dist/report/render/ui/words/onboarding-words.js +446 -0
- package/dist/report/render/ui/words/projects-words.js +217 -0
- package/dist/report/render/ui/words/report-words.js +2099 -0
- package/dist/report/render/ui/words/settings-words.js +611 -0
- package/dist/report/render/ui/words/to-fix-words.js +287 -0
- package/dist/report/report-model.js +2 -0
- package/dist/report/report-use-case.js +1 -0
- package/dist/report/rule-names.js +20 -0
- package/dist/report/session-report.js +163 -0
- package/dist/report/start/app-nav.js +37 -0
- package/dist/report/start/conversations/calendar-view.js +104 -0
- package/dist/report/start/conversations/conversation-columns.js +187 -0
- package/dist/report/start/conversations/conversations-renderer.js +54 -0
- package/dist/report/start/conversations/empty-week.js +120 -0
- package/dist/report/start/conversations/files-window.js +166 -0
- package/dist/report/start/conversations/period-section.js +280 -0
- package/dist/report/start/conversations/periods-script.js +440 -0
- package/dist/report/start/conversations/periods.js +110 -0
- package/dist/report/start/conversations/week-view.js +30 -0
- package/dist/report/start/conversations/weeks.js +17 -0
- package/dist/report/start/month/month-renderer.js +50 -0
- package/dist/report/start/month/month-view.js +135 -0
- package/dist/report/start/month/months.js +17 -0
- package/dist/report/start/onboarding/done.js +171 -0
- package/dist/report/start/onboarding/finish-onboarding.js +93 -0
- package/dist/report/start/onboarding/intro.js +100 -0
- package/dist/report/start/onboarding/onboarding-changes.js +40 -0
- package/dist/report/start/onboarding/onboarding-renderer.js +195 -0
- package/dist/report/start/onboarding/onboarding-script.js +400 -0
- package/dist/report/start/onboarding/onboarding-view.js +98 -0
- package/dist/report/start/onboarding/project-step.js +210 -0
- package/dist/report/start/onboarding/steps.js +239 -0
- package/dist/report/start/onboarding/welcome.js +66 -0
- package/dist/report/start/page-notice.js +10 -0
- package/dist/report/start/projects/index-projects.js +43 -0
- package/dist/report/start/projects/moved-page.js +82 -0
- package/dist/report/start/projects/project-switch.js +138 -0
- package/dist/report/start/projects/projects-window.js +67 -0
- package/dist/report/start/projects/temporary-space.js +18 -0
- package/dist/report/start/render/air-datepicker-vendor.js +27 -0
- package/dist/report/start/render/session-status.js +41 -0
- package/dist/report/start/render/start-words.js +84 -0
- package/dist/report/start/repository.js +28 -0
- package/dist/report/start/serve/index-handler.js +372 -0
- package/dist/report/start/serve/onboarding-request.js +50 -0
- package/dist/report/start/serve/settings-request.js +1 -0
- package/dist/report/start/serve/settings-setup.js +193 -0
- package/dist/report/start/session-index.js +1 -0
- package/dist/report/start/session-start.js +974 -0
- package/dist/report/start/settings/alerts-tab.js +104 -0
- package/dist/report/start/settings/files-tab.js +169 -0
- package/dist/report/start/settings/general-tab.js +71 -0
- package/dist/report/start/settings/settings-renderer.js +254 -0
- package/dist/report/start/settings/settings-script.js +161 -0
- package/dist/report/start/settings/settings-view.js +240 -0
- package/dist/report/start/settings/settings-windows.js +334 -0
- package/dist/report/start/settings-files.js +43 -0
- package/dist/report/start/to-fix/done-list.js +45 -0
- package/dist/report/start/to-fix/file-window.js +110 -0
- package/dist/report/start/to-fix/to-fix-list.js +70 -0
- package/dist/report/start/to-fix/to-fix-renderer.js +237 -0
- package/dist/report/start/to-fix/to-fix-script.js +94 -0
- package/dist/report/start/to-fix/to-fix-view.js +124 -0
- package/dist/report/watch/agent-alert.js +72 -0
- package/dist/report/watch/notice-choices.js +35 -0
- package/dist/report/watch/notice-settings.js +65 -0
- package/dist/report/watch/preferences.js +100 -0
- package/dist/report/watch/render/instruction-words.js +54 -0
- package/dist/report/watch/render/notice-words.js +182 -0
- package/dist/report/watch/subagent-watch.js +345 -0
- package/dist/report/watch/watch-notice.js +1 -0
- package/dist/setup/behind.js +18 -0
- package/dist/setup/codex-mirror.js +171 -0
- package/dist/setup/not-a-project.js +19 -0
- package/dist/setup/project-setup.js +644 -0
- package/dist/setup/protected-patterns.js +30 -0
- package/dist/shared/colour.js +23 -0
- package/dist/shared/compare.js +4 -0
- package/dist/shared/content-version.js +13 -0
- package/dist/shared/counter.js +11 -0
- package/dist/shared/file-stamp.js +4 -0
- package/dist/shared/json.js +13 -0
- package/dist/shared/label.js +13 -0
- package/dist/shared/package-name.js +6 -0
- package/dist/shared/plain-invocation.js +44 -0
- package/dist/shared/plain-version.js +21 -0
- package/dist/shared/printable.js +25 -0
- package/dist/shared/renderer.js +1 -0
- package/dist/shared/sentence.js +20 -0
- package/dist/shared/terminal-logo.js +41 -0
- package/dist/shared/text-table.js +15 -0
- package/dist/shared/wrap.js +29 -0
- package/package.json +70 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { escapeHtml as e } from '../html-report-components.js';
|
|
2
|
+
import { inLanguages } from '../report-copy.js';
|
|
3
|
+
/**
|
|
4
|
+
* File names as chips (guidelines §4 "File name chip"), at most `visible` of them, the rest behind "+N more"
|
|
5
|
+
* (§9.1). `shown` is what a chip says - the path itself, or its name with the whole path as its tooltip. Without a
|
|
6
|
+
* script every chip is shown and the button is not: the extra ones are hidden only by the class a script adds, so a
|
|
7
|
+
* page that runs nothing still names every file.
|
|
8
|
+
*/
|
|
9
|
+
export function fileChips(paths, visible = 2, shown = (path) => path) {
|
|
10
|
+
const chips = paths.map((path, at) => '<span class="chip' + (at >= visible ? ' chip-extra' : '') + '" title="' + e(path) + '">' + e(shown(path)) + '</span>').join('');
|
|
11
|
+
const more = paths.length <= visible ? '' :
|
|
12
|
+
'<button type="button" class="chip-more js-only" data-chip-more aria-expanded="false">' +
|
|
13
|
+
'<span class="chip-closed">' + inLanguages((t) => t('chip.more', { n: paths.length - visible })) + '</span>' +
|
|
14
|
+
'<span class="chip-open">' + inLanguages((t) => t('chip.less')) + '</span></button>';
|
|
15
|
+
return '<span class="chips">' + chips + more + '</span>';
|
|
16
|
+
}
|
|
17
|
+
export const FILE_CHIP_STYLE = String.raw `
|
|
18
|
+
.chips{display:flex;gap:6px;flex-wrap:wrap;min-width:0}
|
|
19
|
+
.chip{font-family:var(--mono);font-size:13px;font-weight:500;color:var(--text);background:var(--white-07);border:1px solid var(--white-10);border-radius:6px;padding:2px 7px;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
20
|
+
.js .chips:not(.chips-open) .chip-extra{display:none}
|
|
21
|
+
.chip-more{position:relative;z-index:2;font:inherit;font-size:13px;font-weight:600;color:var(--text-2);background:transparent;border:1px dashed var(--white-28);border-radius:6px;padding:2px 8px;cursor:pointer;white-space:nowrap}
|
|
22
|
+
.chip-more:hover{color:var(--text);background:var(--white-06);border-color:var(--white-32)}
|
|
23
|
+
.chip-open{display:none}.chips-open .chip-open{display:inline}.chips-open .chip-closed{display:none}
|
|
24
|
+
`;
|
|
25
|
+
/** Opens and closes the extra chips of one cell, and nothing else: the row under it is a link and must not follow. */
|
|
26
|
+
export const FILE_CHIP_SCRIPT = String.raw `
|
|
27
|
+
document.addEventListener('click', (event) => {
|
|
28
|
+
const button = event.target.closest('[data-chip-more]');
|
|
29
|
+
if (!button) return;
|
|
30
|
+
event.preventDefault();
|
|
31
|
+
event.stopPropagation();
|
|
32
|
+
const chips = button.closest('.chips');
|
|
33
|
+
const open = !chips.classList.contains('chips-open');
|
|
34
|
+
chips.classList.toggle('chips-open', open);
|
|
35
|
+
button.setAttribute('aria-expanded', String(open));
|
|
36
|
+
});
|
|
37
|
+
`;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { inLanguages } from '../report-copy.js';
|
|
2
|
+
export function foldLine(spec) {
|
|
3
|
+
return '<details class="fold' + (spec.mark === undefined ? '' : ' fold-' + spec.mark.tone) + '"' + (spec.attributes ?? '') + '><summary class="fold-line">' +
|
|
4
|
+
'<span class="fold-mark" aria-hidden="true">' + (spec.mark?.glyph ?? '✓') + '</span><span class="fold-text">' + spec.summary + '</span>' +
|
|
5
|
+
'<span class="fold-toggle"><span class="fold-show">' + inLanguages((t) => t('fold.show')) + '</span>' +
|
|
6
|
+
'<span class="fold-hide">' + inLanguages((t) => t('fold.hide')) + '</span></span></summary>' +
|
|
7
|
+
'<div class="fold-body">' + spec.body + '</div></details>';
|
|
8
|
+
}
|
|
9
|
+
export const FOLD_LINE_STYLE = String.raw `
|
|
10
|
+
.fold-line{display:flex;align-items:center;gap:12px;width:100%;padding:16px 22px;border-radius:14px;border:1px dashed var(--white-16);color:var(--text-soft);font-size:15px;cursor:pointer;list-style:none}
|
|
11
|
+
.fold-line::-webkit-details-marker{display:none}
|
|
12
|
+
.fold-line:hover{border-color:var(--white-32)}
|
|
13
|
+
.fold[open]>.fold-line{margin-bottom:16px}
|
|
14
|
+
.fold-mark{flex:none;width:24px;height:24px;border-radius:50%;background:var(--mint-14);color:var(--mint);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700}
|
|
15
|
+
.fold-text{flex:1}
|
|
16
|
+
.fold-text strong{font-weight:650;color:var(--text)}
|
|
17
|
+
.fold-text .fold-rest{color:var(--text-2)}
|
|
18
|
+
.fold-toggle{font-size:14px;font-weight:600;color:var(--text-2)}
|
|
19
|
+
.fold-hide{display:none}.fold[open] .fold-hide{display:inline}.fold[open] .fold-show{display:none}
|
|
20
|
+
.fold-sand>.fold-line{border:1px solid var(--sand-35);background:var(--card)}.fold-sand>.fold-line:hover{border-color:var(--sand)}
|
|
21
|
+
.fold-sand .fold-mark{width:32px;height:32px;background:var(--sand-16);color:var(--sand)}.fold-sand .fold-mark svg{width:16px;height:16px}
|
|
22
|
+
`;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { pill } from './button.js';
|
|
2
|
+
const LOCK_SVG = '<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">' +
|
|
3
|
+
'<rect x="5" y="11" width="14" height="10" rx="2.5"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg>';
|
|
4
|
+
export function guideCard(spec) {
|
|
5
|
+
const action = spec.action;
|
|
6
|
+
const shown = action !== undefined && (spec.tone === 'coral' || action.light === true);
|
|
7
|
+
return '<div class="guide guide-' + spec.tone + (shown && spec.tone === 'mint' ? ' guide-acts' : '') + '">' +
|
|
8
|
+
'<span class="guide-mark" aria-hidden="true">' + (spec.tone === 'coral' ? '✦' : spec.mark === 'lock' ? LOCK_SVG : '✓') + '</span>' +
|
|
9
|
+
'<div class="guide-text"><div class="guide-title">' + spec.title + '</div><div class="guide-body">' + spec.body + '</div></div>' +
|
|
10
|
+
(!shown ? '' : pill({ label: action.label, href: action.href, tone: action.light === true ? 'light' : 'primary', size: 'lg',
|
|
11
|
+
...(action.attributes === undefined ? {} : { attributes: action.attributes }) })) +
|
|
12
|
+
'</div>';
|
|
13
|
+
}
|
|
14
|
+
export const GUIDE_CARD_STYLE = String.raw `
|
|
15
|
+
.guide{display:flex;align-items:center;gap:18px;flex-wrap:wrap;padding:20px 24px;border-radius:14px;margin-bottom:36px}
|
|
16
|
+
.guide-coral{background:var(--coral-06);border:1px solid var(--coral-30)}
|
|
17
|
+
.guide-mint{background:var(--mint-08);border:1px solid var(--mint-30);flex-wrap:nowrap}
|
|
18
|
+
.guide-mark{flex:none;width:44px;height:44px;border-radius:50%;display:flex;align-items:center;justify-content:center}
|
|
19
|
+
.guide-coral .guide-mark{background:var(--coral-14);border:1px solid var(--coral-35);color:var(--coral-text);font-size:18px}
|
|
20
|
+
.guide-mint .guide-mark{background:var(--mint);color:var(--on-mint);font-size:20px;font-weight:700}
|
|
21
|
+
.guide-coral .guide-text,.guide-acts .guide-text{flex:1;min-width:220px}
|
|
22
|
+
.guide-mint.guide-acts{flex-wrap:wrap}.guide-mint .guide-mark svg{display:block}
|
|
23
|
+
.guide-title{font-size:17px;font-weight:600;margin-bottom:3px}
|
|
24
|
+
.guide-body{font-size:14px;line-height:1.5;color:var(--text-2)}
|
|
25
|
+
`;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const TICK_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round">' +
|
|
2
|
+
'<path d="M5 12.5l4.5 4.5L19 7.5"/></svg>';
|
|
3
|
+
const MARKS = {
|
|
4
|
+
tick: '<span class="hero-tick" aria-hidden="true">' + TICK_SVG + '</span>',
|
|
5
|
+
unsure: '<span class="hero-unsure" aria-hidden="true">?</span>',
|
|
6
|
+
alert: '<span class="hero-alert" aria-hidden="true">!</span>',
|
|
7
|
+
todo: '<span class="hero-alert" aria-hidden="true" data-hero-todo>!</span>' +
|
|
8
|
+
'<span class="hero-tick" aria-hidden="true" data-hero-done hidden>' + TICK_SVG + '</span>',
|
|
9
|
+
};
|
|
10
|
+
export function hero(spec) {
|
|
11
|
+
return '<section class="hero' + (spec.centred === true ? ' hero-centred' : '') + '">' + (spec.mark === undefined ? '' : MARKS[spec.mark]) +
|
|
12
|
+
(spec.eyebrow === '' ? '' : '<div class="hero-eyebrow"><span class="hero-rule" aria-hidden="true"></span>' +
|
|
13
|
+
'<span class="hero-eyebrow-text">' + spec.eyebrow + '</span></div>') +
|
|
14
|
+
'<h1 class="hero-fact' + (spec.action === '' ? ' hero-alone' : '') + '">' + spec.fact + '</h1>' +
|
|
15
|
+
(spec.action === '' ? '' : '<p class="hero-action' + (spec.calm === true ? ' hero-calm' : spec.mark === 'unsure' ? ' hero-amber' : '') + '" role="doc-subtitle">' + spec.action + '</p>') +
|
|
16
|
+
'<p class="hero-lead">' + spec.lead + '</p></section>';
|
|
17
|
+
}
|
|
18
|
+
export const HERO_STYLE = String.raw `
|
|
19
|
+
.hero{margin-bottom:32px}
|
|
20
|
+
.hero-eyebrow{display:inline-flex;align-items:center;gap:12px;color:var(--coral-text);font-size:15px;font-weight:500;margin-bottom:16px}
|
|
21
|
+
.hero-rule{width:36px;height:1.5px;background:var(--coral)}
|
|
22
|
+
.hero-eyebrow-text{white-space:nowrap}
|
|
23
|
+
.hero-fact,.hero-action{margin:0;font-size:44px;line-height:1.12;letter-spacing:-0.02em;font-weight:650;text-wrap:balance}
|
|
24
|
+
.hero-action{margin:0 0 16px;color:var(--coral)}
|
|
25
|
+
.hero-fact.hero-alone{margin-bottom:12px;font-size:40px}
|
|
26
|
+
.hero-action.hero-calm{color:var(--mint)}.hero-action.hero-amber{color:var(--amber)}
|
|
27
|
+
.hero-lead{margin:0;max-width:600px;font-size:17px;line-height:1.55;color:var(--text-2);text-wrap:pretty}
|
|
28
|
+
.hero-tick{display:flex;align-items:center;justify-content:center;width:72px;height:72px;margin:0 0 26px;border-radius:50%;background:var(--mint);color:var(--on-mint);box-shadow:0 0 0 10px var(--mint-06),0 0 48px var(--mint-16)}
|
|
29
|
+
.hero-tick svg{width:34px;height:34px}
|
|
30
|
+
.hero-alert{display:flex;align-items:center;justify-content:center;width:72px;height:72px;margin:0 0 26px;border-radius:50%;background:var(--coral-14);border:1.5px solid var(--coral-35);color:var(--coral-text);box-shadow:0 0 0 10px var(--coral-06);font-size:34px;font-weight:700}
|
|
31
|
+
.hero-tick[hidden],.hero-alert[hidden]{display:none}
|
|
32
|
+
.hero-unsure{display:flex;align-items:center;justify-content:center;width:72px;height:72px;margin:0 0 26px;border-radius:50%;background:var(--amber-12);border:1.5px solid var(--amber-35);color:var(--amber);box-shadow:0 0 0 10px var(--amber-12);font-size:34px;font-weight:700}
|
|
33
|
+
.hero-centred .hero-tick,.hero-centred .hero-unsure,.hero-centred .hero-alert{margin-left:auto;margin-right:auto}
|
|
34
|
+
.hero-centred{text-align:center;margin-bottom:36px}.hero-centred .hero-eyebrow{margin-bottom:18px}
|
|
35
|
+
.hero-centred .hero-action{margin-bottom:18px}.hero-centred .hero-lead{max-width:520px;margin:0 auto}
|
|
36
|
+
@media (max-width:640px){.hero-fact,.hero-action{font-size:32px}.hero-tick{width:60px;height:60px}.hero-tick svg{width:28px;height:28px}.hero-unsure,.hero-alert{width:60px;height:60px;font-size:28px}}
|
|
37
|
+
`;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function labelledSelect(question, choices, attributes) {
|
|
2
|
+
return '<label class="ls"><span class="ls-q">' + question + '</span><select class="ls-select"' + attributes + '>' +
|
|
3
|
+
choices.map((choice) => '<option value="' + choice.value + '">' + choice.label + '</option>').join('') + '</select></label>';
|
|
4
|
+
}
|
|
5
|
+
export const LABELLED_SELECT_STYLE = String.raw `
|
|
6
|
+
.ls{display:inline-flex;align-items:center;gap:8px;background:var(--card);border:1px solid var(--white-10);border-radius:10px;padding:0 6px 0 14px}
|
|
7
|
+
.ls-q{font-size:13.5px;color:var(--text-3);white-space:nowrap}
|
|
8
|
+
.ls-select{background:transparent;border:none;color:var(--text);font:inherit;font-size:14px;font-weight:600;padding:9px 6px;outline:none;cursor:pointer}
|
|
9
|
+
.ls-select option{background:var(--card)}
|
|
10
|
+
`;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { LANGS, translator } from '../report-copy.js';
|
|
2
|
+
/**
|
|
3
|
+
* Live pages (`.ai/specs/2026-09-24-live-pages.md`): the pill that says there is more, and the line that says the server
|
|
4
|
+
* has gone, both hidden until the script shows them. Every page carries them; only a served page ever shows them.
|
|
5
|
+
*/
|
|
6
|
+
export function liveParts() {
|
|
7
|
+
const words = JSON.stringify(liveWords()).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<');
|
|
8
|
+
return '<button type="button" class="pill pill-light pill-sm live-pill" data-live-pill data-live-words="' + words + '" hidden></button>' +
|
|
9
|
+
'<p class="live-stopped" data-live-stopped role="status" hidden></p>';
|
|
10
|
+
}
|
|
11
|
+
/** The words the script says, per language, with the plural forms each language has (as `report-copy.ts` counts). */
|
|
12
|
+
export function liveWords() {
|
|
13
|
+
const forms = { en: ['one', 'other'], pl: ['one', 'few', 'many'], de: ['one', 'other'] };
|
|
14
|
+
return Object.fromEntries(LANGS.map((lang) => {
|
|
15
|
+
const t = translator(lang);
|
|
16
|
+
return [lang, {
|
|
17
|
+
...Object.fromEntries(forms[lang].map((form) => [form, t('live.new.' + form)])),
|
|
18
|
+
...Object.fromEntries(forms[lang].map((form) => ['go.' + form, t('live.go.' + form)])),
|
|
19
|
+
updated: t('live.updated'),
|
|
20
|
+
stopped: t('live.stopped'),
|
|
21
|
+
}];
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
export const LIVE_STYLE = String.raw `
|
|
25
|
+
.live-pill{position:fixed;top:calc(18px + env(safe-area-inset-top));left:50%;transform:translateX(-50%);z-index:20;box-shadow:0 10px 30px var(--shadow)}
|
|
26
|
+
.live-pill[hidden],.live-stopped[hidden]{display:none}
|
|
27
|
+
.live-stopped{position:fixed;left:50%;bottom:calc(18px + env(safe-area-inset-bottom));transform:translateX(-50%);z-index:20;margin:0;max-width:min(560px,calc(100% - 32px));padding:10px 16px;border-radius:12px;background:var(--popup);border:1px solid var(--white-14);color:var(--text-2);font-size:13.5px;line-height:1.5;text-align:center}
|
|
28
|
+
.live-new{background:var(--coral-08);transition:background 1.6s ease}
|
|
29
|
+
.live-pill.live-foot{top:auto;bottom:calc(22px + env(safe-area-inset-bottom))}
|
|
30
|
+
.live-lit{animation:live-lit 2.4s ease-out}
|
|
31
|
+
@keyframes live-lit{0%,35%{background:var(--coral-14)}100%{background:transparent}}
|
|
32
|
+
@media (prefers-reduced-motion:reduce){.live-lit{animation:none;background:var(--coral-08)}}
|
|
33
|
+
`;
|
|
34
|
+
/**
|
|
35
|
+
* L5-L14. Nothing happens unless the page was served and stamped with its version (L2): a file, a shared page and a
|
|
36
|
+
* report opened alone are left exactly as they were. A page that can take a new version in place (L7a: Conversations,
|
|
37
|
+
* `window.agentwhyLiveSwap`) is handed it, fetched, wherever the reader is and whatever is open: nothing flashes and
|
|
38
|
+
* nothing under the reader moves, what changed is lit, and a new conversation out of view is said at the foot of the
|
|
39
|
+
* screen, where a click brings it into view (the maintainer, 2026-09-25: the pill at the top went unseen). Every other
|
|
40
|
+
* page, and one whose new version it cannot take in place, is updated as follows. The page asks every 2 s while it is visible and every 60 s while
|
|
41
|
+
* it is not (LD1, LD4); the same version does nothing, and nothing is drawn while it asks (L6). A new version is put in
|
|
42
|
+
* place only where nothing can move under the reader (L7) - no window open, no field focused, nothing selected, at the
|
|
43
|
+
* top - and otherwise waits behind the pill (L8), which a click, or scrolling back to the top with nothing open, turns
|
|
44
|
+
* into the update. The update is a reload that keeps the fields, folds and pressed buttons marked `data-live-keep`, the
|
|
45
|
+
* scroll, and lights what is new among the elements marked `data-live-key` (L9). Three failed asks in a row say the
|
|
46
|
+
* server has gone, once, and stop (L13).
|
|
47
|
+
*/
|
|
48
|
+
export const LIVE_SCRIPT = String.raw `
|
|
49
|
+
(() => {
|
|
50
|
+
const html = document.documentElement;
|
|
51
|
+
let sent = html.dataset.version;
|
|
52
|
+
if (!sent || (location.protocol !== 'http:' && location.protocol !== 'https:')) return;
|
|
53
|
+
const pill = document.querySelector('[data-live-pill]');
|
|
54
|
+
const gone = document.querySelector('[data-live-stopped]');
|
|
55
|
+
if (!pill || !gone) return;
|
|
56
|
+
// The page's own name; empty where it was opened at the served root, which the server reads as the list (L3).
|
|
57
|
+
const file = location.pathname.split('/').pop() || '';
|
|
58
|
+
const store = 'agentwhy.live.' + (file || '/');
|
|
59
|
+
const words = JSON.parse(pill.dataset.liveWords || '{}');
|
|
60
|
+
const say = (key) => ((words[html.dataset.lang] || words.en || {})[key] || '');
|
|
61
|
+
const form = (n) => {
|
|
62
|
+
if (n === 1) return 'one';
|
|
63
|
+
if (html.dataset.lang !== 'pl') return 'other';
|
|
64
|
+
const last = n % 10, lastTwo = n % 100;
|
|
65
|
+
return last >= 2 && last <= 4 && !(lastTwo >= 12 && lastTwo <= 14) ? 'few' : 'many';
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const kept = () => [...document.querySelectorAll('[data-live-keep]')];
|
|
69
|
+
const keys = () => [...document.querySelectorAll('[data-live-key]')].map((element) => element.dataset.liveKey);
|
|
70
|
+
const stateOf = (element) => element.tagName === 'DETAILS' ? { open: element.open }
|
|
71
|
+
: element.hasAttribute('aria-pressed') ? { pressed: element.getAttribute('aria-pressed') === 'true' }
|
|
72
|
+
: element.type === 'checkbox' ? { checked: element.checked } : { value: element.value };
|
|
73
|
+
|
|
74
|
+
// After an update: the reader's place back, and what is new lit (L7, L9). Put back once the page's own scripts have
|
|
75
|
+
// run: they come after this one, and a pill pressed or a field changed before they listen is pressed for nobody.
|
|
76
|
+
setTimeout(() => { try {
|
|
77
|
+
const saved = JSON.parse(sessionStorage.getItem(store) || 'null');
|
|
78
|
+
if (saved) {
|
|
79
|
+
sessionStorage.removeItem(store);
|
|
80
|
+
kept().forEach((element, at) => {
|
|
81
|
+
const state = saved.kept[at];
|
|
82
|
+
if (!state) return;
|
|
83
|
+
if ('open' in state) element.open = state.open;
|
|
84
|
+
else if ('pressed' in state) { if (state.pressed && element.getAttribute('aria-pressed') !== 'true') element.click(); }
|
|
85
|
+
else {
|
|
86
|
+
if ('checked' in state) element.checked = state.checked; else element.value = state.value;
|
|
87
|
+
element.dispatchEvent(new Event('input', { bubbles: true }));
|
|
88
|
+
element.dispatchEvent(new Event('change', { bubbles: true }));
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
const before = new Set(saved.keys);
|
|
92
|
+
document.querySelectorAll('[data-live-key]').forEach((element) => {
|
|
93
|
+
if (before.has(element.dataset.liveKey)) return;
|
|
94
|
+
element.classList.add('live-new');
|
|
95
|
+
setTimeout(() => element.classList.remove('live-new'), 1600);
|
|
96
|
+
});
|
|
97
|
+
window.scrollTo(0, saved.scroll || 0);
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {} }, 0);
|
|
100
|
+
|
|
101
|
+
const field = (element) => !!element && (element.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(element.tagName));
|
|
102
|
+
const idle = () => !document.querySelector('dialog[open]') && !field(document.activeElement) &&
|
|
103
|
+
!String(window.getSelection ? window.getSelection() : '') && window.scrollY <= 80 && document.visibilityState === 'visible';
|
|
104
|
+
|
|
105
|
+
let waiting = null;
|
|
106
|
+
let misses = 0;
|
|
107
|
+
let stopped = false;
|
|
108
|
+
let timer = 0;
|
|
109
|
+
|
|
110
|
+
const apply = (toTop) => {
|
|
111
|
+
try {
|
|
112
|
+
sessionStorage.setItem(store, JSON.stringify({ kept: kept().map(stateOf), keys: keys(), scroll: toTop ? 0 : window.scrollY }));
|
|
113
|
+
} catch (error) {}
|
|
114
|
+
location.reload();
|
|
115
|
+
};
|
|
116
|
+
const show = (answer) => {
|
|
117
|
+
// How many conversations the page was sent with is on its root (L2); the answer says how many there are now.
|
|
118
|
+
const fresh = typeof answer.conversations === 'number' ? answer.conversations - Number(html.dataset.conversations || answer.conversations) : 0;
|
|
119
|
+
pill.textContent = fresh > 0 ? say(form(fresh)).replace('{n}', String(fresh)) : say('updated');
|
|
120
|
+
pill.dataset.toTop = fresh > 0 ? 'true' : '';
|
|
121
|
+
pill.hidden = false;
|
|
122
|
+
};
|
|
123
|
+
const stop = () => {
|
|
124
|
+
stopped = true;
|
|
125
|
+
html.dataset.live = 'gone';
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
pill.hidden = true;
|
|
128
|
+
gone.textContent = say('stopped');
|
|
129
|
+
gone.hidden = false;
|
|
130
|
+
};
|
|
131
|
+
const later = (answer) => { if (idle()) apply(false); else show(answer); };
|
|
132
|
+
// The words each language keeps for its attributes, for what was just put in place (as LANGUAGE_SCRIPT writes them).
|
|
133
|
+
const relabel = () => {
|
|
134
|
+
const lang = html.dataset.lang || 'en';
|
|
135
|
+
const cap = lang[0].toUpperCase() + lang[1];
|
|
136
|
+
document.querySelectorAll('[data-label-' + lang + ']').forEach((element) => element.setAttribute('aria-label', element.dataset['label' + cap]));
|
|
137
|
+
document.querySelectorAll('[data-placeholder-' + lang + ']').forEach((element) => element.setAttribute('placeholder', element.dataset['placeholder' + cap]));
|
|
138
|
+
};
|
|
139
|
+
const light = (element) => {
|
|
140
|
+
element.classList.remove('live-lit');
|
|
141
|
+
void element.offsetWidth;
|
|
142
|
+
element.classList.add('live-lit');
|
|
143
|
+
setTimeout(() => element.classList.remove('live-lit'), 2600);
|
|
144
|
+
};
|
|
145
|
+
const inView = (element) => {
|
|
146
|
+
if (element.hidden || element.offsetParent === null) return false;
|
|
147
|
+
const box = element.getBoundingClientRect();
|
|
148
|
+
return box.bottom > 0 && box.top < window.innerHeight;
|
|
149
|
+
};
|
|
150
|
+
// L7a: the page as the server has it now, put in place by the page's own script.
|
|
151
|
+
let going = null;
|
|
152
|
+
let swapping = false;
|
|
153
|
+
const swapIn = (answer) => { swapping = true; return fetch(location.pathname, { cache: 'no-store' })
|
|
154
|
+
.then((response) => (response.ok ? response.text() : Promise.reject(new Error(String(response.status)))))
|
|
155
|
+
.then((text) => {
|
|
156
|
+
const next = new DOMParser().parseFromString(text, 'text/html');
|
|
157
|
+
const done = window.agentwhyLiveSwap(next);
|
|
158
|
+
if (!done) { later(answer); return; }
|
|
159
|
+
sent = next.documentElement.dataset.version || answer.version;
|
|
160
|
+
html.dataset.version = sent;
|
|
161
|
+
if (next.documentElement.dataset.conversations) html.dataset.conversations = next.documentElement.dataset.conversations;
|
|
162
|
+
waiting = null;
|
|
163
|
+
relabel();
|
|
164
|
+
done.fresh.forEach(light);
|
|
165
|
+
const unseen = done.added.filter((row) => !inView(row));
|
|
166
|
+
if (unseen.length === 0) { if (!going || inView(going.row)) hideGo(); return; }
|
|
167
|
+
going = { row: unseen[0], reveal: done.reveal };
|
|
168
|
+
pill.textContent = say('go.' + form(done.added.length)).replace('{n}', String(done.added.length));
|
|
169
|
+
pill.dataset.toTop = '';
|
|
170
|
+
pill.classList.add('live-foot', 'pill-primary');
|
|
171
|
+
pill.classList.remove('pill-light');
|
|
172
|
+
pill.hidden = false;
|
|
173
|
+
})
|
|
174
|
+
.catch(() => later(answer))
|
|
175
|
+
.finally(() => { swapping = false; }); };
|
|
176
|
+
const hideGo = () => {
|
|
177
|
+
going = null;
|
|
178
|
+
pill.hidden = true;
|
|
179
|
+
pill.classList.remove('live-foot', 'pill-primary');
|
|
180
|
+
pill.classList.add('pill-light');
|
|
181
|
+
};
|
|
182
|
+
const ask = () => fetch('api/version/' + file, { cache: 'no-store' })
|
|
183
|
+
.then((response) => {
|
|
184
|
+
// which-project V14, amended: this project's pages were given up for another's, in this process - said at once.
|
|
185
|
+
if (response.status === 410) { stop(); return null; }
|
|
186
|
+
return response.ok ? response.json() : Promise.reject(new Error(String(response.status)));
|
|
187
|
+
})
|
|
188
|
+
.then((answer) => {
|
|
189
|
+
misses = 0;
|
|
190
|
+
// What a page may say while the server answers: "Connected" on an empty week (the maintainer's design, 2026-09-29).
|
|
191
|
+
if (answer) html.dataset.live = 'on';
|
|
192
|
+
if (!answer || answer.version === sent || swapping) return;
|
|
193
|
+
waiting = answer;
|
|
194
|
+
if (typeof window.agentwhyLiveSwap === 'function') return swapIn(answer);
|
|
195
|
+
later(answer);
|
|
196
|
+
})
|
|
197
|
+
.catch(() => { misses += 1; if (misses >= 3) stop(); });
|
|
198
|
+
const loop = () => {
|
|
199
|
+
if (stopped) return;
|
|
200
|
+
clearTimeout(timer);
|
|
201
|
+
ask().finally(() => { if (!stopped) timer = setTimeout(loop, document.visibilityState === 'visible' ? 2000 : 60000); });
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
pill.addEventListener('click', () => {
|
|
205
|
+
if (!going) { apply(pill.dataset.toTop === 'true'); return; }
|
|
206
|
+
const { row, reveal } = going;
|
|
207
|
+
hideGo();
|
|
208
|
+
if (!row.isConnected) return;
|
|
209
|
+
reveal(row);
|
|
210
|
+
row.scrollIntoView({ block: 'center', behavior: matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth' });
|
|
211
|
+
light(row);
|
|
212
|
+
});
|
|
213
|
+
const whenIdle = () => {
|
|
214
|
+
if (going && inView(going.row)) { light(going.row); hideGo(); }
|
|
215
|
+
if (waiting && !swapping && idle()) apply(false);
|
|
216
|
+
};
|
|
217
|
+
window.addEventListener('scroll', whenIdle, { passive: true });
|
|
218
|
+
document.addEventListener('close', whenIdle, true);
|
|
219
|
+
document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible' && !stopped) loop(); });
|
|
220
|
+
timer = setTimeout(loop, 2000);
|
|
221
|
+
})();
|
|
222
|
+
`;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const DAY = 86_400_000;
|
|
2
|
+
const LOCALES = { en: 'en-US', pl: 'pl-PL', de: 'de-DE' };
|
|
3
|
+
/** The calendar date a day number stands for. Day numbers are arithmetic only; no zone applies to them. */
|
|
4
|
+
export function dayOf(number) {
|
|
5
|
+
const date = new Date(number * DAY);
|
|
6
|
+
return { number, year: date.getUTCFullYear(), month: date.getUTCMonth() + 1, day: date.getUTCDate(), weekday: (date.getUTCDay() + 6) % 7 };
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Reads a moment as a calendar day and a time in one zone. An unknown zone name is read as UTC rather than failing a
|
|
10
|
+
* run: a page that says UTC is a page, and a run that stops over a clock setting is not.
|
|
11
|
+
*/
|
|
12
|
+
export function localClock(zone) {
|
|
13
|
+
const options = { year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' };
|
|
14
|
+
let format;
|
|
15
|
+
try {
|
|
16
|
+
format = new Intl.DateTimeFormat('en-US', { ...options, timeZone: zone });
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
format = new Intl.DateTimeFormat('en-US', { ...options, timeZone: 'UTC' });
|
|
20
|
+
}
|
|
21
|
+
return (epoch) => {
|
|
22
|
+
const parts = Object.fromEntries(format.formatToParts(new Date(epoch)).map((part) => [part.type, part.value]));
|
|
23
|
+
const number = Math.floor(Date.UTC(Number(parts['year']), Number(parts['month']) - 1, Number(parts['day'])) / DAY);
|
|
24
|
+
return { day: dayOf(number), time: (parts['hour'] ?? '00').padStart(2, '0') + ':' + (parts['minute'] ?? '00').padStart(2, '0') };
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const at = (day) => new Date(day.number * DAY);
|
|
28
|
+
/** "Mon, Sep 21" - a day that is neither today nor yesterday. */
|
|
29
|
+
export function dayName(day, lang) {
|
|
30
|
+
return new Intl.DateTimeFormat(LOCALES[lang], { timeZone: 'UTC', weekday: 'short', month: 'short', day: 'numeric' }).format(at(day));
|
|
31
|
+
}
|
|
32
|
+
/** "Mon 21" - the label of a day tile. */
|
|
33
|
+
export function tileName(day, lang) {
|
|
34
|
+
const parts = new Intl.DateTimeFormat(LOCALES[lang], { timeZone: 'UTC', weekday: 'short', day: 'numeric' }).formatToParts(at(day));
|
|
35
|
+
const part = (type) => parts.find((each) => each.type === type)?.value ?? '';
|
|
36
|
+
return part('weekday') + ' ' + part('day');
|
|
37
|
+
}
|
|
38
|
+
/** "Sep 21 – 27", "Sep 28 – Oct 4" - a week, in the order each language writes a range. */
|
|
39
|
+
export function rangeName(first, last, lang) {
|
|
40
|
+
return new Intl.DateTimeFormat(LOCALES[lang], { timeZone: 'UTC', month: 'short', day: 'numeric' }).formatRange(at(first), at(last));
|
|
41
|
+
}
|
|
42
|
+
/** "September 2026" - a month, as each language names one standing alone. */
|
|
43
|
+
export function monthName(day, lang) {
|
|
44
|
+
const name = new Intl.DateTimeFormat(LOCALES[lang], { timeZone: 'UTC', month: 'long', year: 'numeric' }).format(at(day));
|
|
45
|
+
return name.charAt(0).toLocaleUpperCase(LOCALES[lang]) + name.slice(1);
|
|
46
|
+
}
|
|
47
|
+
/** "Mon" … "Sun" - the head of a calendar, Monday first. 2024-01-01 was a Monday. */
|
|
48
|
+
export function weekdayNames(lang) {
|
|
49
|
+
const format = new Intl.DateTimeFormat(LOCALES[lang], { timeZone: 'UTC', weekday: 'short' });
|
|
50
|
+
return Array.from({ length: 7 }, (_unused, offset) => format.format(new Date(Date.UTC(2024, 0, 1 + offset))));
|
|
51
|
+
}
|
|
52
|
+
/** The first and the last day of the month a day falls in. */
|
|
53
|
+
export function monthOf(day) {
|
|
54
|
+
return {
|
|
55
|
+
first: dayOf(Math.floor(Date.UTC(day.year, day.month - 1, 1) / DAY)),
|
|
56
|
+
last: dayOf(Math.floor(Date.UTC(day.year, day.month, 0) / DAY)),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const MODE_SVG = {
|
|
2
|
+
block: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' +
|
|
3
|
+
'<rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7.5a4 4 0 0 1 8 0V11"/></svg>',
|
|
4
|
+
tell: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' +
|
|
5
|
+
'<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></svg>',
|
|
6
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { FAVICON_LINK } from '../html-head.js';
|
|
2
|
+
import { DEFAULT_LANG, translator } from '../report-copy.js';
|
|
3
|
+
import { backdrop, BACKDROP_STYLE } from './backdrop.js';
|
|
4
|
+
import { BUTTON_STYLE } from './button.js';
|
|
5
|
+
import { CONFIRM_DIALOG_STYLE } from './confirm-dialog.js';
|
|
6
|
+
import { liveParts, LIVE_SCRIPT, LIVE_STYLE } from './live-script.js';
|
|
7
|
+
import { POPUP_SCRIPT, POPUP_STYLE } from './popup.js';
|
|
8
|
+
import { THEME_COLOUR, TOKENS_STYLE } from './tokens.js';
|
|
9
|
+
import { UPDATE_NOTICE_SCRIPT, UPDATE_NOTICE_STYLE } from './update-notice.js';
|
|
10
|
+
/** What the update notice is drawn and worked with: its window is the kit's confirmation. */
|
|
11
|
+
const NOTICE_STYLES = [BUTTON_STYLE, POPUP_STYLE, CONFIRM_DIALOG_STYLE, UPDATE_NOTICE_STYLE];
|
|
12
|
+
const NOTICE_SCRIPTS = [POPUP_SCRIPT, UPDATE_NOTICE_SCRIPT];
|
|
13
|
+
/**
|
|
14
|
+
* The document every page of the new design is: head, policy, icon, the tokens, the page's own pieces' styles, the
|
|
15
|
+
* background, the sidebar beside the content, and the scripts. The `js` class is set before any content, so a piece
|
|
16
|
+
* that only a script can work shows its control and hides its fallback in the same paint.
|
|
17
|
+
*/
|
|
18
|
+
export function pageShell(page) {
|
|
19
|
+
const windows = page.windows ?? [];
|
|
20
|
+
return [
|
|
21
|
+
'<!doctype html><html lang="' + DEFAULT_LANG + '" data-lang="' + DEFAULT_LANG + '"><head><meta charset="utf-8">',
|
|
22
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
23
|
+
'<meta name="color-scheme" content="dark"><meta name="theme-color" content="' + THEME_COLOUR + '">',
|
|
24
|
+
page.policy,
|
|
25
|
+
FAVICON_LINK,
|
|
26
|
+
'<title>' + translator(DEFAULT_LANG)(page.title) + '</title>',
|
|
27
|
+
'<style>' + [TOKENS_STYLE, BACKDROP_STYLE, SHELL_STYLE, LIVE_STYLE, ...new Set([...page.styles, ...(page.notice === undefined ? [] : NOTICE_STYLES), ...windows.flatMap((one) => one.styles)])].join('') + '</style></head>',
|
|
28
|
+
'<body><script>document.documentElement.classList.add(\'js\');' + EARLY_LANGUAGE_SCRIPT + '</script>',
|
|
29
|
+
backdrop(),
|
|
30
|
+
// Live pages: hidden until a served page has something new to say (`live-pages` L8, L13).
|
|
31
|
+
page.live === false ? '' : liveParts(),
|
|
32
|
+
page.notice ?? '',
|
|
33
|
+
...windows.map((one) => one.html),
|
|
34
|
+
'<div class="shell">' + (page.sidebar ?? '') + '<main class="shell-main' + (page.width === undefined ? '' : ' shell-' + page.width) + '" id="main">' + page.main + '</main></div>',
|
|
35
|
+
...[...new Set(page.libraries ?? [])].map((library) => '<script>' + library + '</script>'),
|
|
36
|
+
'<script>' + [LANGUAGE_SCRIPT, ...(page.live === false ? [] : [LIVE_SCRIPT]), ...new Set([...page.scripts, ...(page.notice === undefined ? [] : NOTICE_SCRIPTS), ...windows.flatMap((one) => one.scripts)])].join('\n') + '</script>',
|
|
37
|
+
'</body></html>',
|
|
38
|
+
'',
|
|
39
|
+
].join('\n');
|
|
40
|
+
}
|
|
41
|
+
const SHELL_STYLE = String.raw `
|
|
42
|
+
.shell{position:relative;z-index:1;min-height:100vh;display:flex}
|
|
43
|
+
.shell-main{flex:1;min-width:0;max-width:1240px;margin:0 auto;padding:40px 32px 96px}
|
|
44
|
+
.shell-narrow{max-width:780px}.shell-list{max-width:880px}
|
|
45
|
+
@media (max-width:860px){.shell{flex-direction:column}.shell-main{width:100%;padding:28px 16px 72px}}
|
|
46
|
+
`;
|
|
47
|
+
/** The key the chosen language is kept under, so a later page can start where this one left off. */
|
|
48
|
+
const LANG_STORAGE_KEY = 'agentwhy.lang';
|
|
49
|
+
/**
|
|
50
|
+
* Folded into the `js` marker script, which runs before the sidebar and main content are parsed: the styles are
|
|
51
|
+
* already known by then (the `<style>` block precedes `<body>`), so setting `data-lang` here is early enough that
|
|
52
|
+
* the language picked on an earlier page applies to this one's first paint, with no separate `<script>` tag to
|
|
53
|
+
* count against §7.5's "one self-contained file, one inline enhancement script".
|
|
54
|
+
*/
|
|
55
|
+
const EARLY_LANGUAGE_SCRIPT = String.raw `
|
|
56
|
+
(() => {
|
|
57
|
+
try {
|
|
58
|
+
const stored = localStorage.getItem('${LANG_STORAGE_KEY}');
|
|
59
|
+
if (stored === 'pl' || stored === 'de') {
|
|
60
|
+
document.documentElement.dataset.lang = stored;
|
|
61
|
+
document.documentElement.lang = stored;
|
|
62
|
+
}
|
|
63
|
+
} catch (e) {}
|
|
64
|
+
})();
|
|
65
|
+
`;
|
|
66
|
+
/**
|
|
67
|
+
* Switching language changes which copy is visible; attributes, which cannot hold three languages, are rewritten
|
|
68
|
+
* from the one each element keeps per language. The choice is kept in storage, so the next page opened starts in
|
|
69
|
+
* the same language instead of falling back to English. Where the page is served, it is also written to the
|
|
70
|
+
* person's notice choices, so the line agentwhy leaves in the conversation speaks the same language
|
|
71
|
+
* (`the-agent-tells-you.md` R29). Nothing waits on that write, and a page with nowhere to send it sends nothing.
|
|
72
|
+
*/
|
|
73
|
+
const LANGUAGE_SCRIPT = String.raw `
|
|
74
|
+
(() => {
|
|
75
|
+
const select = document.getElementById('lang');
|
|
76
|
+
const apply = (next) => {
|
|
77
|
+
const cap = next[0].toUpperCase() + next[1];
|
|
78
|
+
document.documentElement.dataset.lang = next;
|
|
79
|
+
document.documentElement.lang = next;
|
|
80
|
+
document.querySelectorAll('[data-label-' + next + ']').forEach((element) => {
|
|
81
|
+
element.setAttribute('aria-label', element.dataset['label' + cap]);
|
|
82
|
+
});
|
|
83
|
+
document.querySelectorAll('[data-placeholder-' + next + ']').forEach((element) => {
|
|
84
|
+
element.setAttribute('placeholder', element.dataset['placeholder' + cap]);
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
const stored = document.documentElement.dataset.lang;
|
|
88
|
+
if (stored && stored !== 'en') apply(stored);
|
|
89
|
+
if (select && stored) select.value = stored;
|
|
90
|
+
if (!select) return;
|
|
91
|
+
select.addEventListener('change', () => {
|
|
92
|
+
const next = select.value;
|
|
93
|
+
apply(next);
|
|
94
|
+
try { localStorage.setItem('${LANG_STORAGE_KEY}', next); } catch (e) {}
|
|
95
|
+
if (location.protocol === 'http:' || location.protocol === 'https:') {
|
|
96
|
+
fetch('api/notify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ scope: 'everywhere', lang: next }) })
|
|
97
|
+
.catch(() => {});
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
})();
|
|
101
|
+
`;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** `state` is the page row whose pills say, with a dot, whether what each one opens is on (Settings). */
|
|
2
|
+
export function pillTabs(tabs, variant, selected = 0, aside = '') {
|
|
3
|
+
return '<div class="tabs" data-tabs>' + (aside === '' ? '' : '<div class="tabs-top">') +
|
|
4
|
+
'<div class="tabs-bar tabs-' + variant + ' js-only" role="tablist">' + tabs.map((tab, at) => '<button type="button" class="tabs-pill' + (at === selected ? ' tabs-on' : '') + '" role="tab" data-tab="' + at + '"' +
|
|
5
|
+
' aria-selected="' + String(at === selected) + '">' +
|
|
6
|
+
(tab.dot === undefined ? '' : '<span class="tabs-dot tabs-dot-' + tab.dot + '" aria-hidden="true"></span>') + tab.label + '</button>').join('') + '</div>' +
|
|
7
|
+
// Beside the pills, what belongs to the whole row rather than to one tab (To fix: "Your protection worked").
|
|
8
|
+
(aside === '' ? '' : aside + '</div>') +
|
|
9
|
+
tabs.map((tab, at) => '<section class="tabs-panel' + (at === selected ? ' tabs-on' : '') + '" role="tabpanel" data-tab-panel="' + at + '">' +
|
|
10
|
+
'<h3 class="tabs-fallback">' + tab.label + '</h3>' + tab.panel + '</section>').join('') +
|
|
11
|
+
'</div>';
|
|
12
|
+
}
|
|
13
|
+
export const PILL_TABS_STYLE = String.raw `
|
|
14
|
+
.tabs-bar{display:flex;padding:3px;border-radius:999px;width:fit-content}
|
|
15
|
+
.js .tabs-bar.js-only{display:flex}
|
|
16
|
+
.tabs-page{background:var(--card);border:1px solid var(--white-09);margin-bottom:22px}
|
|
17
|
+
.tabs-top{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:22px}
|
|
18
|
+
.tabs-top .tabs-bar{margin-bottom:0}
|
|
19
|
+
.tabs-inset{gap:4px;background:var(--panel);border:1px solid var(--white-08);margin-bottom:24px}
|
|
20
|
+
.tabs-pill{border:none;border-radius:999px;font:inherit;font-size:14px;cursor:pointer;background:transparent;color:var(--text-2)}
|
|
21
|
+
.tabs-page .tabs-pill{padding:8px 18px;font-weight:600}
|
|
22
|
+
.tabs-inset .tabs-pill{padding:7px 16px;font-weight:500}
|
|
23
|
+
.tabs-state{padding:4px;background:var(--card);border:1px solid var(--white-09);margin-bottom:22px}
|
|
24
|
+
.tabs-state .tabs-pill{display:flex;align-items:center;gap:8px;padding:9px 18px;font-size:14.5px;font-weight:600;white-space:nowrap}
|
|
25
|
+
@media (max-width:420px){.tabs-state .tabs-pill{padding:9px 13px}}
|
|
26
|
+
.tabs-dot{width:7px;height:7px;border-radius:50%}.tabs-dot-mint{background:var(--mint)}.tabs-dot-grey{background:var(--white-25)}
|
|
27
|
+
.tabs-pill.tabs-on{background:var(--text);color:var(--bg)}
|
|
28
|
+
.js .tabs-panel:not(.tabs-on){display:none}
|
|
29
|
+
.tabs-fallback{margin:24px 0 12px;font-size:16px;font-weight:650}
|
|
30
|
+
.js .tabs-fallback{display:none}
|
|
31
|
+
`;
|
|
32
|
+
/** Switches the panel of the tab row it was pressed in, and nothing else. */
|
|
33
|
+
export const PILL_TABS_SCRIPT = String.raw `
|
|
34
|
+
document.addEventListener('click', (event) => {
|
|
35
|
+
const tab = event.target.closest('[data-tab]');
|
|
36
|
+
if (!tab) return;
|
|
37
|
+
const group = tab.closest('[data-tabs]');
|
|
38
|
+
const at = tab.getAttribute('data-tab');
|
|
39
|
+
// The pills are the row's own, whether or not something stands beside them (an aside, in .tabs-top).
|
|
40
|
+
group.querySelectorAll(':scope > .tabs-bar [data-tab], :scope > .tabs-top > .tabs-bar [data-tab]').forEach((each) => {
|
|
41
|
+
const on = each === tab;
|
|
42
|
+
each.classList.toggle('tabs-on', on);
|
|
43
|
+
each.setAttribute('aria-selected', String(on));
|
|
44
|
+
});
|
|
45
|
+
group.querySelectorAll(':scope > [data-tab-panel]').forEach((panel) => panel.classList.toggle('tabs-on', panel.getAttribute('data-tab-panel') === at));
|
|
46
|
+
});
|
|
47
|
+
`;
|