@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,135 @@
|
|
|
1
|
+
import { dataTable } from '../../render/ui/data-table.js';
|
|
2
|
+
import { closeButton, pill } from '../../render/ui/button.js';
|
|
3
|
+
import { CLOSES, popup, popupFoot } from '../../render/ui/popup.js';
|
|
4
|
+
import { dayName, monthName, weekdayNames } from '../../render/ui/local-date.js';
|
|
5
|
+
import { inLanguages, labelAttributes } from '../../render/report-copy.js';
|
|
6
|
+
import { statusIcon } from '../../render/ui/status-icon.js';
|
|
7
|
+
import { LOOKS } from '../../render/ui/status-look.js';
|
|
8
|
+
import { dayTile, periodSwitch } from '../conversations/calendar-view.js';
|
|
9
|
+
import { CONVERSATION_TABLE, conversationRow } from '../conversations/conversation-columns.js';
|
|
10
|
+
const MONTH_SWITCH_WORDS = {
|
|
11
|
+
heading: 'month.heading',
|
|
12
|
+
earlier: 'month.earlier',
|
|
13
|
+
later: 'month.later',
|
|
14
|
+
back: 'month.back',
|
|
15
|
+
pick: 'month.pick',
|
|
16
|
+
hint: 'month.hint',
|
|
17
|
+
};
|
|
18
|
+
/** "This month", "Last month", "3 months ago" - counted by the calendar, so a skipped empty month is still counted. */
|
|
19
|
+
export function monthAgo(month, t) {
|
|
20
|
+
return month.ago === 0 ? t('month.this') : month.ago === 1 ? t('month.last') : t('month.ago', { n: month.ago });
|
|
21
|
+
}
|
|
22
|
+
/** "This month · September 2026", in every language. */
|
|
23
|
+
export function monthTitle(month) {
|
|
24
|
+
return inLanguages((t, lang) => monthAgo(month, t) + ' · ' + monthName(month.first, lang));
|
|
25
|
+
}
|
|
26
|
+
/** The window a day opens in. Day numbers are unique on the page, whichever month they fall in. */
|
|
27
|
+
const dayWindow = (day) => 'day-' + day.day.number;
|
|
28
|
+
/**
|
|
29
|
+
* "Your month": the switcher between months and a calendar, Monday first, one tile per day - the week's tiles
|
|
30
|
+
* (F11), laid out as the month falls. A tile names only its day's number; the weekdays head the columns. A day
|
|
31
|
+
* with work opens in a window over the page (guidelines §1.4: details after a click, in a popup); nothing is listed
|
|
32
|
+
* under the calendar.
|
|
33
|
+
*/
|
|
34
|
+
export function monthView(months, at, widen, include = false) {
|
|
35
|
+
const month = months[at];
|
|
36
|
+
return '<section class="cw-week">' +
|
|
37
|
+
periodSwitch('month', months, at, MONTH_SWITCH_WORDS, (each, t, lang) => monthAgo(each, t) + ' <span class="cw-switch-range">· ' + monthName(each.first, lang) + '</span>') +
|
|
38
|
+
'<div class="mv-cal">' +
|
|
39
|
+
'<div class="mv-head" aria-hidden="true">' +
|
|
40
|
+
Array.from({ length: 7 }, (_unused, weekday) => '<span>' + inLanguages((_t, lang) => weekdayNames(lang)[weekday] ?? '') + '</span>').join('') +
|
|
41
|
+
'</div>' +
|
|
42
|
+
'<div class="mv-grid">' +
|
|
43
|
+
'<span class="mv-blank" aria-hidden="true"></span>'.repeat(month.first.weekday) +
|
|
44
|
+
month.days.map((day) => dayTile(day, String(day.day.day), { opens: dayWindow(day), extra: dayTip(day) })).join('') +
|
|
45
|
+
'</div></div>' +
|
|
46
|
+
month.days.filter((day) => day.conversations > 0).map((day) => dayWindowOf(day, month.conversations, widen, include)).join('') +
|
|
47
|
+
'</section>';
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* What a day held, on hover or keyboard focus: its date and how many conversations, then how many in each look - read
|
|
51
|
+
* something private, only saw a name, stopped, nothing private - in the icons and colours the rows use, and that a
|
|
52
|
+
* click shows them. Drawn with no script; hidden from a screen reader, which is given the same in the window a click opens.
|
|
53
|
+
*/
|
|
54
|
+
function dayTip(day) {
|
|
55
|
+
if (day.conversations === 0)
|
|
56
|
+
return '';
|
|
57
|
+
// Near the calendar's edges the summary opens inward, so it is never cut by the window.
|
|
58
|
+
const side = day.day.weekday < 2 ? 'start' : day.day.weekday > 4 ? 'end' : 'middle';
|
|
59
|
+
const rows = lookCounts(day).map(([look, n]) => '<span class="mv-tip-row">' + statusIcon(look) + '<b>' + String(n) + '</b></span>').join('');
|
|
60
|
+
return '<span class="mv-tip mv-tip-' + side + '" aria-hidden="true">' +
|
|
61
|
+
'<span class="mv-tip-head">' + inLanguages((t, lang) => dayName(day.day, lang) + ' · ' + t('month.day.count', { n: day.conversations })) + '</span>' +
|
|
62
|
+
rows +
|
|
63
|
+
'<span class="mv-tip-hint">' + inLanguages((t) => t('month.tip.open')) + '</span></span>';
|
|
64
|
+
}
|
|
65
|
+
/** The looks a day holds, in the order of `LOOKS`, each with how many of its conversations are in it. */
|
|
66
|
+
function lookCounts(day) {
|
|
67
|
+
return Object.keys(LOOKS).flatMap((look) => ((day.looks[look] ?? 0) > 0 ? [[look, day.looks[look] ?? 0]] : []));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* One day, opened in a wide window over the page: its date, how many conversations and how many to check, the same
|
|
71
|
+
* count by look the tile's summary gives, and those conversations in the rows Conversations lists them in - the ones
|
|
72
|
+
* that read something private first, then the rest, each newest first. A row's "See all {n} files" opens the page's
|
|
73
|
+
* one files window over this one, as it does on Conversations (F58).
|
|
74
|
+
*/
|
|
75
|
+
function dayWindowOf(day, conversations, widen, include) {
|
|
76
|
+
const those = conversations.filter((item) => item.day.number === day.day.number);
|
|
77
|
+
const ordered = [...those.filter((item) => LOOKS[item.look].attention), ...those.filter((item) => !LOOKS[item.look].attention)];
|
|
78
|
+
const id = dayWindow(day);
|
|
79
|
+
const head = '<div class="mv-pp-head">' +
|
|
80
|
+
'<div class="mv-pp-top"><span class="mv-pp-dot mv-pp-dot-' + (day.toCheck > 0 || day.fixed > 0 ? 'coral' : 'mint') + '" aria-hidden="true"></span>' +
|
|
81
|
+
'<span class="mv-pp-sub">' + inLanguages((t) => t('month.day.count', { n: day.conversations }) + ' · ' +
|
|
82
|
+
(day.toCheck > 0 ? t('conv.day.toCheck', { n: day.toCheck }) : day.fixed > 0 ? t('conv.day.fixed', { n: day.fixed }) : t('conv.day.good'))) + '</span>' +
|
|
83
|
+
closeButton(labelAttributes((t) => t('app.close')) + CLOSES) + '</div>' +
|
|
84
|
+
'<h2 class="mv-pp-title" id="' + id + '-title">' + inLanguages((_t, lang) => dayName(day.day, lang)) + '</h2>' +
|
|
85
|
+
'<div class="mv-pp-looks">' + lookCounts(day).map(([look, n]) => '<span class="mv-pp-look">' + statusIcon(look) + '<b>' + String(n) + '</b></span>').join('') + '</div>' +
|
|
86
|
+
'</div>';
|
|
87
|
+
const body = '<div class="mv-pp-body">' +
|
|
88
|
+
dataTable({ ...CONVERSATION_TABLE, rows: ordered.map((item) => conversationRow(item, widen, include)) }) + '</div>';
|
|
89
|
+
const foot = popupFoot(inLanguages((t) => t('month.window.note')), pill({ label: inLanguages((t) => t('app.close')), tone: 'outline', size: 'md', button: true, attributes: CLOSES }));
|
|
90
|
+
return popup({ id, size: 'wide', labelledBy: id + '-title', body: head + body + foot });
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Seven columns at every width: a calendar that wraps is no calendar. Where a column grows too narrow for "2 to
|
|
94
|
+
* check", the words give way to the dot, which carries the same colour.
|
|
95
|
+
*/
|
|
96
|
+
export const MONTH_VIEW_STYLE = String.raw `
|
|
97
|
+
.mv-cal{container-type:inline-size}
|
|
98
|
+
.mv-head,.mv-grid{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:8px}
|
|
99
|
+
.mv-head{margin-bottom:8px}
|
|
100
|
+
.mv-head span{font-size:12.5px;font-weight:600;color:var(--text-3);padding-left:14px}
|
|
101
|
+
.mv-grid .cw-day{min-width:0;min-height:108px;gap:8px}
|
|
102
|
+
.mv-grid .cw-day-state{min-height:1.4em}
|
|
103
|
+
.mv-grid .cw-day-said{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
104
|
+
.mv-grid .cw-day-today .cw-day-label{color:var(--text)}
|
|
105
|
+
.mv-grid .cw-day{position:relative}
|
|
106
|
+
.mv-tip{position:absolute;bottom:calc(100% + 8px);z-index:5;width:250px;display:flex;flex-direction:column;gap:8px;padding:14px 16px;border-radius:14px;
|
|
107
|
+
background:var(--popup);border:1px solid var(--white-10);box-shadow:0 24px 60px var(--shadow);text-align:left;white-space:normal;
|
|
108
|
+
opacity:0;visibility:hidden;pointer-events:none;transition:opacity .12s,visibility .12s}
|
|
109
|
+
.mv-tip-start{left:0}.mv-tip-end{right:0}.mv-tip-middle{left:50%;translate:-50% 0}
|
|
110
|
+
.cw-day:hover .mv-tip,.cw-day:focus-visible .mv-tip{opacity:1;visibility:visible}
|
|
111
|
+
.mv-tip-head{font-size:13px;font-weight:600;color:var(--text-2);margin-bottom:2px}
|
|
112
|
+
.mv-tip-row{display:flex;align-items:center;justify-content:space-between;gap:12px}
|
|
113
|
+
.mv-tip-row .look-label{font-size:13.5px}
|
|
114
|
+
.mv-tip-row b{font-size:15px;font-weight:650;color:var(--text)}
|
|
115
|
+
.mv-tip-hint{font-size:12.5px;color:var(--text-3);padding-top:8px;border-top:1px solid var(--white-07)}
|
|
116
|
+
@media (hover:none){.mv-tip{display:none}}
|
|
117
|
+
.mv-pp-head{padding:28px 32px 20px}
|
|
118
|
+
.mv-pp-top{display:flex;align-items:center;gap:12px}
|
|
119
|
+
.mv-pp-dot{flex:none;width:10px;height:10px;border-radius:50%}
|
|
120
|
+
.mv-pp-dot-coral{background:var(--coral)}.mv-pp-dot-mint{background:var(--mint)}
|
|
121
|
+
.mv-pp-sub{flex:1;font-size:14px;font-weight:600;color:var(--text-2)}
|
|
122
|
+
.mv-pp-title{margin:10px 0 0;font-size:30px;line-height:1.15;font-weight:650;letter-spacing:-0.01em}
|
|
123
|
+
.mv-pp-looks{display:flex;flex-wrap:wrap;gap:10px 22px;margin-top:18px}
|
|
124
|
+
.mv-pp-look{display:flex;align-items:center;gap:10px}
|
|
125
|
+
.mv-pp-look b{font-size:15px;font-weight:650;color:var(--text)}
|
|
126
|
+
.mv-pp-body{padding:0 32px 28px;overflow-x:auto}
|
|
127
|
+
@media (max-width:640px){.mv-pp-head{padding:20px 18px 16px}.mv-pp-title{font-size:24px}.mv-pp-body{padding:0 18px 20px}}
|
|
128
|
+
@container (max-width:760px){
|
|
129
|
+
.mv-head span{padding-left:0;text-align:center}
|
|
130
|
+
.mv-grid{gap:5px}
|
|
131
|
+
.mv-grid .cw-day{min-height:0;padding:8px 4px;align-items:center;gap:6px;border-radius:10px}
|
|
132
|
+
.mv-grid .cw-day-count{font-size:16px}
|
|
133
|
+
.mv-grid .cw-day-said{display:none}
|
|
134
|
+
}
|
|
135
|
+
`;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { dayOf, monthOf } from '../../render/ui/local-date.js';
|
|
2
|
+
import { bucketed, conversationsOf, period } from '../conversations/periods.js';
|
|
3
|
+
/**
|
|
4
|
+
* The months the This month page is read through (`.ai/plans/2026-09-23-month-redesign.md` T2): calendar months in the
|
|
5
|
+
* time zone of the machine that ran `start` (O2), the current month first, then every earlier month that holds a
|
|
6
|
+
* conversation.
|
|
7
|
+
*/
|
|
8
|
+
export function conversationMonths(index) {
|
|
9
|
+
const { today, conversations } = conversationsOf(index);
|
|
10
|
+
const counted = today.year * 12 + today.month;
|
|
11
|
+
const byMonth = bucketed(conversations, (item) => counted - (item.day.year * 12 + item.day.month));
|
|
12
|
+
return [...byMonth].map(([ago, those]) => {
|
|
13
|
+
// `Date.UTC` carries a month below 0 into the years before, so any `ago` names its month.
|
|
14
|
+
const { first, last } = monthOf(dayOf(Math.floor(Date.UTC(today.year, today.month - 1 - ago, 1) / 86_400_000)));
|
|
15
|
+
return period(ago, first.number, last.number, today.number, those);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { escapeHtml as e } from '../../render/html-report-components.js';
|
|
2
|
+
import { inLanguages } from '../../render/report-copy.js';
|
|
3
|
+
import { BRAND_MARK } from '../../render/ui/brand-mark.js';
|
|
4
|
+
import { pill } from '../../render/ui/button.js';
|
|
5
|
+
/**
|
|
6
|
+
* Done (`.ai/specs/2026-09-24-onboarding.md` W17-W20, W1a): what the run found, and one action. Which of its three
|
|
7
|
+
* versions is drawn is the model's to say - files to fix, nothing to fix, or no chats yet. What depends on Finish -
|
|
8
|
+
* whether alerts are on, what failed, the summary line - is left for the page's script to fill (`data-ob-if`, `data-ob-*`),
|
|
9
|
+
* since only the server's answer knows it. It is never shown without a script (W27).
|
|
10
|
+
*/
|
|
11
|
+
export function doneScreen(done, links) {
|
|
12
|
+
const fix = done.conversations > 0 && done.total > 0;
|
|
13
|
+
const version = done.conversations === 0 ? empty(links) : fix ? toFix(done, links) : nothing(done, links);
|
|
14
|
+
return '<section class="ob-screen ob-done" data-ob-screen="done" hidden aria-labelledby="ob-done-title">' +
|
|
15
|
+
(fix ? knotMark() : tickMark()) +
|
|
16
|
+
version +
|
|
17
|
+
'<ul class="ob-failed" data-ob-failed hidden>' +
|
|
18
|
+
['watch', 'protect', 'search', 'mode', 'notices'].map((change) => '<li data-ob-fail="' + change + '" hidden><span class="ob-fail-mark" aria-hidden="true">!</span><span>' +
|
|
19
|
+
inLanguages((t) => t('ob.failed.' + change)) + ' <span class="ob-fail-said" data-ob-fail-said></span> ' +
|
|
20
|
+
'<a class="ob-link" href="' + links.settings + '">' + inLanguages((t) => t('ob.failed.go')) + '</a></span></li>').join('') + '</ul>' +
|
|
21
|
+
// W20a: Codex, where the project uses it and Claude Code's block runs - filled by the script from Finish's answer.
|
|
22
|
+
'<p class="ob-codex" data-ob-codex="on" hidden>' + inLanguages((t) => t('ob.done.codex.on')) + '</p>' +
|
|
23
|
+
'<p class="ob-codex" data-ob-codex="off" hidden>' + inLanguages((t) => t('ob.done.codex.off')) + ' <a class="ob-link" href="' + links.settings + '">' +
|
|
24
|
+
inLanguages((t) => t('ob.done.codex.go')) + '</a></p>' +
|
|
25
|
+
'<p class="ob-summary"><span class="ob-summary-tick" data-ob-summary-tick aria-hidden="true">✓</span> ' +
|
|
26
|
+
inLanguages(() => '<span data-ob-summary></span>') + ' · <a class="ob-link" href="' + links.settings + '">' +
|
|
27
|
+
inLanguages((t) => t('ob.summary.change')) + '</a></p>' +
|
|
28
|
+
'<p class="ob-unrecorded" data-ob-unrecorded hidden>' + inLanguages((t) => t('ob.unrecorded')) + '</p>' +
|
|
29
|
+
'</section>';
|
|
30
|
+
}
|
|
31
|
+
/** W17: the mint ✓, its glow, its ripples and the confetti - for a Done with nothing left to do. */
|
|
32
|
+
function tickMark() {
|
|
33
|
+
return '<div class="ob-done-mark" aria-hidden="true">' + confetti() +
|
|
34
|
+
'<span class="ob-done-glow"></span><span class="ob-ripple"></span><span class="ob-ripple ob-ripple-2"></span>' +
|
|
35
|
+
'<span class="ob-done-tick"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">' +
|
|
36
|
+
'<path d="M5 12.5l4.5 4.5L19 7.5"/></svg></span></div>';
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* W17a: with files to fix, the brand's knot in coral instead. Mint says safe or done (guidelines §2), and this screen
|
|
40
|
+
* says there is something left to do: no ✓, no mint glow, no confetti.
|
|
41
|
+
*/
|
|
42
|
+
function knotMark() {
|
|
43
|
+
return '<div class="ob-done-mark ob-done-mark-fix" aria-hidden="true"><span class="ob-fix-halo"></span>' +
|
|
44
|
+
'<span class="ob-fix-knot">' + BRAND_MARK + '</span></div>';
|
|
45
|
+
}
|
|
46
|
+
/** W18: files to fix. */
|
|
47
|
+
function toFix(done, links) {
|
|
48
|
+
return '<h2 class="ob-h2 ob-done-title" id="ob-done-title">' + inLanguages((t) => t('ob.done.files.title')) + '</h2>' +
|
|
49
|
+
'<p class="ob-done-text">' + told() + ' ' + inLanguages((t) => t('ob.done.already', { n: done.total, when: when(t, done.asked) })) + '</p>' +
|
|
50
|
+
'<div class="ob-found">' +
|
|
51
|
+
'<div class="ob-found-head"><span class="ob-found-mark" aria-hidden="true">!</span><span>' +
|
|
52
|
+
'<span class="ob-found-title">' + inLanguages((t) => t('ob.done.found', { n: done.total })) + '</span>' +
|
|
53
|
+
'<span class="ob-found-sub">' + inLanguages((t) => t(done.state === 'keys' ? 'ob.done.keys' : 'fix.hero.look')) + '</span></span></div>' +
|
|
54
|
+
'<ul class="ob-found-rows">' + done.files.map(foundRow).join('') + '</ul>' +
|
|
55
|
+
(done.more > 0 ? '<p class="ob-found-more">' + inLanguages((t) => t('ob.done.more', { n: done.more })) + '</p>' : '') +
|
|
56
|
+
'<div class="ob-found-foot"><a class="ob-link ob-later" href="' + links.conversations + '">' + inLanguages((t) => t('ob.done.later')) + '</a>' +
|
|
57
|
+
pill({ label: inLanguages((t) => t('ob.done.fix')), tone: 'primary', size: 'lg', href: e(firstReport(done) ?? links.toFix) }) + '</div></div>';
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* W18a: where Fix them now leads - the report of the newest conversation that read the first file to fix, To fix's
|
|
61
|
+
* first (T8), so the fixing starts where it happened. To fix where no report of it was written.
|
|
62
|
+
*/
|
|
63
|
+
function firstReport(done) {
|
|
64
|
+
return done.files[0]?.conversations.find((conversation) => conversation.report !== undefined)?.report;
|
|
65
|
+
}
|
|
66
|
+
/** T6: the rule's name, else "A private file"; the group's colour on its bar (T4); the path as To fix's chip writes it. */
|
|
67
|
+
function foundRow(file) {
|
|
68
|
+
return '<li class="ob-found-row"><span class="ob-found-bar ob-found-bar-' + (file.label === 'rotate' ? 'keys' : 'look') + '" aria-hidden="true"></span>' +
|
|
69
|
+
'<span class="ob-found-name">' + inLanguages((t) => t(file.name === undefined ? 'fix.private' : 'set.rule.' + file.name)) + '</span>' +
|
|
70
|
+
'<span class="chip" title="' + e(file.path) + '">' + e(file.path) + '</span></li>';
|
|
71
|
+
}
|
|
72
|
+
/** W19: nothing to fix - or, where a chat could not be read in full, no claim that there is nothing (W34). */
|
|
73
|
+
function nothing(done, links) {
|
|
74
|
+
return '<h2 class="ob-h2 ob-done-title" id="ob-done-title"><span data-ob-if="on">' + inLanguages((t) => t('ob.done.protected')) + '</span>' +
|
|
75
|
+
'<span data-ob-if="off" hidden>' + inLanguages((t) => t('ob.done.set')) + '</span></h2>' +
|
|
76
|
+
'<p class="ob-done-text"><span data-ob-if="on">' + inLanguages((t) => t('ob.done.knowing')) + '</span>' +
|
|
77
|
+
'<span data-ob-if="off" hidden>' + inLanguages((t) => t('ob.done.notTold')) + '</span></p>' +
|
|
78
|
+
(done.gaps
|
|
79
|
+
? '<div class="ob-good ob-good-grey"><span class="ob-good-mark ob-good-mark-grey" aria-hidden="true">?</span><span>' +
|
|
80
|
+
'<span class="ob-good-title">' + inLanguages((t) => t('ob.done.gaps')) + '</span>' +
|
|
81
|
+
'<a class="ob-link" href="' + links.conversations + '">' + inLanguages((t) => t('ob.done.gaps.go')) + '</a></span></div>'
|
|
82
|
+
: '<div class="ob-good"><span class="ob-good-mark" aria-hidden="true">✓</span><span>' +
|
|
83
|
+
'<span class="ob-good-title">' + inLanguages((t) => t('ob.done.good')) + '</span>' +
|
|
84
|
+
'<span class="ob-good-text">' + inLanguages((t) => t('ob.done.good.text', { when: when(t, done.asked) })) + '</span></span></div>') +
|
|
85
|
+
'<div class="ob-done-go">' + pill({ label: inLanguages((t) => t('ob.done.open')), tone: 'mint', size: 'lg', href: links.conversations }) + '</div>';
|
|
86
|
+
}
|
|
87
|
+
/** W1a: no AI chats yet - set up before there is anything to show, and Settings the one page there is. */
|
|
88
|
+
function empty(links) {
|
|
89
|
+
return '<h2 class="ob-h2 ob-done-title" id="ob-done-title"><span data-ob-if="on">' + inLanguages((t) => t('ob.done.protected')) + '</span>' +
|
|
90
|
+
'<span data-ob-if="off" hidden>' + inLanguages((t) => t('ob.done.empty.title')) + '</span></h2>' +
|
|
91
|
+
'<p class="ob-done-text">' + inLanguages((t) => t('ob.done.empty.text')) + '</p>' +
|
|
92
|
+
'<div class="ob-done-go">' + pill({ label: inLanguages((t) => t('ob.done.settings')), tone: 'mint', size: 'lg', href: links.settings }) + '</div>';
|
|
93
|
+
}
|
|
94
|
+
/** "From now on, you'll be told right away." - or, where alerts end up off, how to turn them on (W18). */
|
|
95
|
+
function told() {
|
|
96
|
+
return '<span data-ob-if="on">' + inLanguages((t) => t('ob.done.told')) + '</span>' +
|
|
97
|
+
'<span data-ob-if="off" hidden>' + inLanguages((t) => t('ob.done.notTold')) + '</span>';
|
|
98
|
+
}
|
|
99
|
+
/** The run's range in words: "in the last 7 days", or "since 2026-09-01" where a date was asked (To fix D3). */
|
|
100
|
+
function when(t, asked) {
|
|
101
|
+
const span = /^(\d+)([hdw])$/.exec(asked);
|
|
102
|
+
return span === null ? t('ob.when.since', { date: e(asked) }) : t('ob.when.' + span[2], { n: Number(span[1]) });
|
|
103
|
+
}
|
|
104
|
+
/** The design's confetti, from its own seed: 34 pieces in the brand's colours. */
|
|
105
|
+
function confetti() {
|
|
106
|
+
let seed = 3;
|
|
107
|
+
const next = () => (seed = (seed * 9301 + 49297) % 233280) / 233280;
|
|
108
|
+
const tones = ['coral', 'coral-soft', 'mint', 'white', 'amber'];
|
|
109
|
+
return Array.from({ length: 34 }, (_unused, at) => {
|
|
110
|
+
const angle = next() * Math.PI * 2;
|
|
111
|
+
const distance = 90 + next() * 160;
|
|
112
|
+
const turn = next() * 540;
|
|
113
|
+
const width = 5 + next() * 5;
|
|
114
|
+
const delay = next() * 0.25;
|
|
115
|
+
return '<span class="ob-conf ob-conf-' + tones[at % tones.length] + '" style="--dx:' + (Math.cos(angle) * distance).toFixed(1) + 'px;--dy:' +
|
|
116
|
+
(Math.sin(angle) * distance - 40).toFixed(1) + 'px;--r:' + turn.toFixed(0) + 'deg;--w:' + width.toFixed(1) + 'px;--d:' + delay.toFixed(2) + 's"></span>';
|
|
117
|
+
}).join('');
|
|
118
|
+
}
|
|
119
|
+
/** Done's own look. Colours come from the tokens only. */
|
|
120
|
+
export const DONE_STYLE = String.raw `
|
|
121
|
+
.ob-done{max-width:640px;margin:0 auto;display:flex;flex-direction:column;align-items:center;text-align:center}
|
|
122
|
+
.ob-done-mark{position:relative;width:96px;height:96px;margin-bottom:14px;display:flex;align-items:center;justify-content:center}
|
|
123
|
+
.ob-done-glow{position:absolute;inset:-50px;border-radius:50%;background:radial-gradient(circle,var(--mint-35),transparent 62%);animation:obGlow 3.5s ease-in-out infinite}
|
|
124
|
+
.ob-ripple{position:absolute;inset:10px;border-radius:50%;border:2px solid var(--mint-50);opacity:0;animation:obRipple 2.8s ease-out .4s infinite}
|
|
125
|
+
.ob-ripple-2{animation-delay:1.8s}
|
|
126
|
+
.ob-conf{position:absolute;left:50%;top:50%;width:var(--w);height:calc(var(--w) * .45);border-radius:2px;animation:obConfetti 1.4s cubic-bezier(.2,.7,.3,1) var(--d) both}
|
|
127
|
+
.ob-conf-coral{background:var(--coral)}.ob-conf-coral-soft{background:var(--coral-link-hover)}.ob-conf-mint{background:var(--mint)}
|
|
128
|
+
.ob-conf-white{background:var(--text)}.ob-conf-amber{background:var(--amber)}
|
|
129
|
+
.ob-done-tick{position:relative;width:88px;height:88px;border-radius:50%;background:radial-gradient(circle at 35% 30%,var(--mint-hover),var(--mint));color:var(--on-mint);display:flex;align-items:center;justify-content:center;box-shadow:0 0 60px var(--mint-50);animation:obPop .7s cubic-bezier(.2,.9,.3,1.3) both}
|
|
130
|
+
.ob-done-tick svg{width:42px;height:42px}
|
|
131
|
+
.ob-done-mark-fix{margin-bottom:22px}
|
|
132
|
+
.ob-fix-halo{position:absolute;inset:-22px;border-radius:50%;border:1px solid var(--coral-12);background:radial-gradient(circle,var(--coral-12),transparent 70%)}
|
|
133
|
+
.ob-fix-knot{position:relative;width:88px;height:88px;border-radius:50%;background:var(--card);border:1px solid var(--coral-45);color:var(--coral);display:flex;align-items:center;justify-content:center;box-shadow:0 0 40px var(--coral-18);animation:obPop .7s cubic-bezier(.2,.9,.3,1.3) both}
|
|
134
|
+
.ob-fix-knot .brand-mark{width:44px;height:44px}
|
|
135
|
+
.ob-done-title{font-size:40px;line-height:1.08;letter-spacing:-.03em;margin-bottom:8px}
|
|
136
|
+
.ob-done-text{margin:0 0 20px;max-width:460px;font-size:16px;line-height:1.5;color:var(--text-2)}
|
|
137
|
+
.ob-found{width:100%;text-align:left;border-radius:18px;background:var(--card);border:1px solid var(--coral-45);box-shadow:0 20px 60px var(--shadow);overflow:hidden}
|
|
138
|
+
.ob-found-head{display:flex;align-items:center;gap:12px;padding:18px 20px;background:var(--coral-07);border-bottom:1px solid var(--coral-18)}
|
|
139
|
+
.ob-found-mark,.ob-fail-mark{flex:none;width:30px;height:30px;border-radius:50%;background:var(--coral);color:var(--on-coral);font-size:15px;font-weight:700;display:flex;align-items:center;justify-content:center}
|
|
140
|
+
.ob-found-title{display:block;font-size:17px;font-weight:650}
|
|
141
|
+
.ob-found-sub{display:block;font-size:14px;color:var(--text-2);margin-top:2px}
|
|
142
|
+
.ob-found-rows{list-style:none;margin:0;padding:0}
|
|
143
|
+
.ob-found-row{display:flex;align-items:center;gap:12px;padding:13px 20px;border-bottom:1px solid var(--white-06)}
|
|
144
|
+
.ob-found-bar{flex:none;width:2px;height:26px;border-radius:4px}
|
|
145
|
+
.ob-found-bar-keys{background:var(--coral)}.ob-found-bar-look{background:var(--amber)}
|
|
146
|
+
.ob-found-name{flex:1;min-width:0;font-size:15px;font-weight:600}
|
|
147
|
+
.ob-found-more{margin:0;padding:12px 20px;font-size:14px;color:var(--text-2);border-bottom:1px solid var(--white-06)}
|
|
148
|
+
.ob-found-foot{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;padding:16px 20px}
|
|
149
|
+
.ob-found-foot .pill{box-shadow:0 10px 30px var(--coral-30)}
|
|
150
|
+
.ob-link{color:var(--text-2);font-weight:600}
|
|
151
|
+
.ob-link:hover{color:var(--text)}
|
|
152
|
+
.ob-good{width:100%;text-align:left;display:flex;align-items:center;gap:14px;padding:20px;border-radius:18px;background:var(--card);border:1px solid var(--mint-35)}
|
|
153
|
+
.ob-good-grey{border-color:var(--white-14)}
|
|
154
|
+
.ob-good-mark{flex:none;width:30px;height:30px;border-radius:50%;background:var(--mint);color:var(--on-mint);font-size:15px;font-weight:700;display:flex;align-items:center;justify-content:center}
|
|
155
|
+
.ob-good-mark-grey{background:var(--white-10);color:var(--text-2)}
|
|
156
|
+
.ob-good-title{display:block;font-size:17px;font-weight:650}
|
|
157
|
+
.ob-good-text{display:block;font-size:14px;color:var(--text-2);margin-top:2px}
|
|
158
|
+
.ob-done-go{margin-top:24px}
|
|
159
|
+
.ob-done-go .pill{padding:15px 32px;font-size:16px;box-shadow:0 10px 40px var(--mint-20);transition:background .15s,border-color .15s,transform .15s,box-shadow .2s}
|
|
160
|
+
.ob-done-go .pill:hover{transform:translateY(-2px);box-shadow:0 14px 44px var(--mint-35)}
|
|
161
|
+
.ob-failed{list-style:none;margin:18px 0 0;padding:0;width:100%;display:grid;gap:8px;text-align:left}
|
|
162
|
+
.ob-failed li{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;border-radius:12px;background:var(--coral-06);border:1px solid var(--coral-30);font-size:14px;line-height:1.5;color:var(--text-soft)}
|
|
163
|
+
.ob-failed li[hidden],.ob-failed[hidden]{display:none}
|
|
164
|
+
.ob-fail-mark{width:22px;height:22px;font-size:12px}
|
|
165
|
+
.ob-fail-said{color:var(--text-2)}
|
|
166
|
+
.ob-summary{margin:14px 0 0;font-size:13.5px;color:var(--text-3)}
|
|
167
|
+
.ob-summary-tick{color:var(--mint)}
|
|
168
|
+
.ob-unrecorded{margin:10px 0 0;font-size:13.5px;color:var(--text-3)}
|
|
169
|
+
.ob-codex{max-width:560px;margin:14px auto 0;font-size:13.5px;line-height:1.5;color:var(--text-3)}
|
|
170
|
+
@media (max-width:640px){.ob-done-title{font-size:30px}.ob-found-foot{flex-direction:column-reverse;align-items:stretch;text-align:center}.ob-conf{--spread:.5}}
|
|
171
|
+
`;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { patternsOf } from '../../../setup/protected-patterns.js';
|
|
2
|
+
import { settingsView } from '../settings/settings-view.js';
|
|
3
|
+
import { onboardingChanges } from './onboarding-changes.js';
|
|
4
|
+
import { deniedIn, inForceOf, rowKey } from './onboarding-view.js';
|
|
5
|
+
/**
|
|
6
|
+
* Finish (`.ai/specs/2026-09-24-onboarding.md` W14-W16, W22): the person's choices, written through the routes Settings
|
|
7
|
+
* already uses - nothing here decides what a file holds - and then the record that the onboarding was finished.
|
|
8
|
+
*
|
|
9
|
+
* The changes are made from the files as they are now, not as they were when the page was drawn, so a choice made in
|
|
10
|
+
* another tab since is never written over. Each change is tried whatever the one before answered, and the record is
|
|
11
|
+
* written whatever they answered: a person who reached Done is not sent through the onboarding again for one failed
|
|
12
|
+
* write, which Done names and Settings can make (W20).
|
|
13
|
+
*/
|
|
14
|
+
export async function finishOnboarding(dependencies, choices) {
|
|
15
|
+
// Refused whole, before anything runs: a request half written would leave a project set up in a way nobody chose.
|
|
16
|
+
const refused = patternsOf([...choices.protect, ...choices.tell]).refused;
|
|
17
|
+
if (refused.length > 0) {
|
|
18
|
+
return { outcome: 'refused', message: refused.map(({ pattern, reason }) => `"${pattern}" was not written: ${reason}.`).join('\n') };
|
|
19
|
+
}
|
|
20
|
+
const changes = onboardingChanges(choices, inForceOf(await dependencies.settingsNow()));
|
|
21
|
+
const results = [];
|
|
22
|
+
// KD2: what goes onto the told list is not blocked by the add that comes before it.
|
|
23
|
+
const telling = new Set(changes.flatMap((change) => (change.change === 'tell' ? change.patterns : [])));
|
|
24
|
+
for (const change of changes)
|
|
25
|
+
results.push(await written(dependencies, change, choices.scope, telling));
|
|
26
|
+
const recorded = await dependencies.store.add(dependencies.clock());
|
|
27
|
+
// W20a: read from the files as Finish left them - the writes above go through `CodexMirror`, which writes Codex's hook
|
|
28
|
+
// with Claude Code's `refuse` - and said only where Claude Code's block runs, so a project that blocks nothing is not
|
|
29
|
+
// told Codex isn't blocked.
|
|
30
|
+
const after = (await dependencies.settingsNow()).hooks;
|
|
31
|
+
const codex = after !== undefined && after.refuse !== false ? after.codex : undefined;
|
|
32
|
+
return { outcome: 'finished', results, recorded, ...(codex === undefined ? {} : { codex }) };
|
|
33
|
+
}
|
|
34
|
+
async function written(dependencies, change, scope, telling) {
|
|
35
|
+
try {
|
|
36
|
+
if (change.change === 'watch') {
|
|
37
|
+
// Installing where it is not also takes it out of the other file (R4g): the move of W15.1 is this same change.
|
|
38
|
+
return fromSetup('watch', await dependencies.settings({ change: 'hooks', hooks: ['watch'], on: true, where: change.where }));
|
|
39
|
+
}
|
|
40
|
+
if (change.change === 'protect') {
|
|
41
|
+
// W15.2: read again. The hook just installed can change which file the hooks read, and so where an add goes and
|
|
42
|
+
// which built-in patterns go with it - Settings' add, decided from what the files hold now.
|
|
43
|
+
const now = await dependencies.settingsNow();
|
|
44
|
+
const view = settingsView(now);
|
|
45
|
+
if (!view.canAdd) {
|
|
46
|
+
return { change: 'protect', written: false, message: 'The files were not added: the hooks read rules this page cannot change.' };
|
|
47
|
+
}
|
|
48
|
+
// KD2: the rows left on Block, found by key, with the rules no file holds now. A file told about keeps no rule (F57).
|
|
49
|
+
const rows = view.rows.filter((row) => row.mode === 'block' && change.rows.includes(rowKey(row)))
|
|
50
|
+
.flatMap((row) => row.patterns.filter((pattern) => !deniedIn(now, pattern)));
|
|
51
|
+
const told = new Set(view.rows.filter((row) => row.mode === 'tell').flatMap((row) => row.patterns));
|
|
52
|
+
const patterns = [...new Set([...view.withBuiltIn, ...rows, ...change.patterns])].filter((pattern) => !told.has(pattern) && !telling.has(pattern));
|
|
53
|
+
if (patterns.length === 0)
|
|
54
|
+
return { change: 'protect', written: true, message: '' };
|
|
55
|
+
return fromSetup('protect', await dependencies.settings({ change: 'adopt', patterns, where: view.addTo }));
|
|
56
|
+
}
|
|
57
|
+
if (change.change === 'search') {
|
|
58
|
+
// W12a: what Settings' **Block searches too** writes - `refuse`, in the file step 1 chose (F38, as amended).
|
|
59
|
+
return fromSetup('search', await dependencies.settings({ change: 'hooks', hooks: ['refuse'], on: true, where: change.where }));
|
|
60
|
+
}
|
|
61
|
+
if (change.change === 'mode' || change.change === 'tell') {
|
|
62
|
+
if (dependencies.mode === undefined) {
|
|
63
|
+
return { change: change.change, written: false, message: 'This run keeps no list of files to tell you about, so nothing was switched.' };
|
|
64
|
+
}
|
|
65
|
+
if (change.change === 'tell') {
|
|
66
|
+
// A name added and switched to Tell me: onto the told list, and no rule is written for it.
|
|
67
|
+
return fromMode('tell', await dependencies.mode({ change: 'mode', to: 'tell', patterns: change.patterns, rules: [], where: scope }));
|
|
68
|
+
}
|
|
69
|
+
// W12a: the row's switch as the files hold it now, never as the page sent it - Settings' own, found by the row's key.
|
|
70
|
+
const row = settingsView(await dependencies.settingsNow()).rows.find((each) => rowKey(each) === change.key);
|
|
71
|
+
const to = row?.switchTo;
|
|
72
|
+
if (to === undefined || to.to !== change.to) {
|
|
73
|
+
return { change: 'mode', written: false, message: 'This file can no longer be switched here. Settings shows it as it is now.' };
|
|
74
|
+
}
|
|
75
|
+
return fromMode('mode', await dependencies.mode({ change: 'mode', to: to.to, patterns: to.patterns, rules: to.rules, where: scope }));
|
|
76
|
+
}
|
|
77
|
+
const answer = await dependencies.notify({
|
|
78
|
+
scope: 'project',
|
|
79
|
+
choices: { ...(change.on === undefined ? {} : { on: change.on }), ...(change.clean === undefined ? {} : { clean: change.clean }) },
|
|
80
|
+
});
|
|
81
|
+
return { change: 'notices', written: answer.written, message: answer.said.trim() };
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
// One change that could not be made does not stop the next one, nor the record (W15, W22).
|
|
85
|
+
return { change: change.change, written: false, message: error instanceof Error ? error.message : String(error) };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function fromSetup(change, answer) {
|
|
89
|
+
return { change, written: answer.outcome === 'written' || answer.outcome === 'unchanged', message: answer.output.trim() };
|
|
90
|
+
}
|
|
91
|
+
function fromMode(change, answer) {
|
|
92
|
+
return { change, written: answer.outcome === 'written' || answer.outcome === 'unchanged', message: answer.output.trim() };
|
|
93
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { inLanguages } from '../../render/report-copy.js';
|
|
2
|
+
/**
|
|
3
|
+
* The intro (`.ai/specs/2026-09-24-onboarding.md` W4-W6): the problem told without a technical word, in about twelve
|
|
4
|
+
* seconds, as the design file's mock plays it. An illustration: fixed words, no file of the person's, nothing from the
|
|
5
|
+
* model (W5). Every moment below is a CSS delay from the moment the screen is shown, so no timer runs it; the page's
|
|
6
|
+
* script only moves on to the welcome at `INTRO_MS`, or at **Skip intro**. It is never drawn without a script, and never
|
|
7
|
+
* under `prefers-reduced-motion` (W29).
|
|
8
|
+
*/
|
|
9
|
+
/** When the welcome takes over (W4). The last thing the intro draws fades out 0.1 s before. */
|
|
10
|
+
export const INTRO_MS = 12_400;
|
|
11
|
+
/**
|
|
12
|
+
* The key the intro shows being read. Made up, and shaped so that no pattern of the redactor reads it as a secret - a
|
|
13
|
+
* test says so (W5) - because a page that carried a key-shaped string would teach the reader nothing and trip the
|
|
14
|
+
* tool's own checks.
|
|
15
|
+
*/
|
|
16
|
+
export const INTRO_KEY = 'KEY-4eC3-9HqL';
|
|
17
|
+
/** The eight files and folders, in the mock's order: the word, a folder or a file, and whether it is one the AI reads. */
|
|
18
|
+
const FILES = [
|
|
19
|
+
{ word: 'photos', folder: true, hot: false },
|
|
20
|
+
{ word: 'invoices', folder: true, hot: false },
|
|
21
|
+
{ word: 'passwords', folder: false, hot: true },
|
|
22
|
+
{ word: 'contract', folder: false, hot: false },
|
|
23
|
+
{ word: 'customers', folder: false, hot: true },
|
|
24
|
+
{ word: 'notes', folder: false, hot: false },
|
|
25
|
+
{ word: 'bank', folder: false, hot: true },
|
|
26
|
+
{ word: 'website', folder: true, hot: false },
|
|
27
|
+
];
|
|
28
|
+
/** Phase 2 starts here, and every line of the chat is placed from it (W4). */
|
|
29
|
+
const CHAT_AT = 3.2;
|
|
30
|
+
const EYE = '<svg class="ob-eye" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">' +
|
|
31
|
+
'<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>';
|
|
32
|
+
export function introScreen() {
|
|
33
|
+
const chips = FILES.map((file, at) => '<span class="ob-chip' + (file.hot ? ' ob-chip-hot' : '') + '" style="--d:' + seconds(0.15 + at * 0.1) +
|
|
34
|
+
(file.hot ? ';--lit:' + seconds(1.3 + 0.3 + at * 0.12) : '') + '">' +
|
|
35
|
+
(file.hot ? '<span class="ob-ping" aria-hidden="true"></span>' : '') +
|
|
36
|
+
'<span class="ob-chip-icon ' + (file.folder ? 'ob-chip-folder' : 'ob-chip-file') + '" aria-hidden="true"></span>' +
|
|
37
|
+
inLanguages((t) => t('ob.intro.' + file.word)) + '</span>').join('');
|
|
38
|
+
const row = (who, text, at) => '<div class="ob-chat-row ob-chat-' + who + '" style="--d:' + seconds(CHAT_AT + at) + '">' +
|
|
39
|
+
(who === 'ai' ? '<span class="ob-av ob-av-ai" aria-hidden="true">AI</span>' : '') +
|
|
40
|
+
'<div class="ob-chat-say"><div class="ob-chat-who">' + inLanguages((t) => t('ob.intro.' + who)) + '</div>' +
|
|
41
|
+
'<div class="ob-bubble">' + text + '</div></div>' +
|
|
42
|
+
(who === 'you' ? '<span class="ob-av" aria-hidden="true">' + inLanguages((t) => t('ob.intro.you')) + '</span>' : '') + '</div>';
|
|
43
|
+
return '<section class="ob-screen ob-intro" data-ob-screen="intro" hidden' + ' aria-label="agentwhy">' +
|
|
44
|
+
'<div class="ob-intro-stage">' +
|
|
45
|
+
'<div class="ob-intro-files">' +
|
|
46
|
+
'<div class="ob-intro-working"><span class="ob-intro-dot" aria-hidden="true"></span>' +
|
|
47
|
+
'<span class="ob-intro-type">' + inLanguages((t) => t('ob.intro.working')) + '</span></div>' +
|
|
48
|
+
'<div class="ob-chips"><span class="ob-beam" aria-hidden="true"></span>' + chips + '</div></div>' +
|
|
49
|
+
'<div class="ob-intro-chat">' +
|
|
50
|
+
row('you', inLanguages((t) => t('ob.intro.ask')), 0.5) +
|
|
51
|
+
row('ai', inLanguages((t) => t('ob.intro.sure')), 1.4) +
|
|
52
|
+
'<div class="ob-hidden-part" style="--d:' + seconds(CHAT_AT + 2.5) + '">' +
|
|
53
|
+
'<div class="ob-hidden-head">' + EYE + inLanguages((t) => t('ob.intro.hidden')) + '</div>' +
|
|
54
|
+
'<div class="ob-bubble ob-bubble-hot" style="--d:' + seconds(CHAT_AT + 3) + '">' + inLanguages((t) => t('ob.intro.opening')) + '</div>' +
|
|
55
|
+
'<div class="ob-bubble ob-bubble-hot" style="--d:' + seconds(CHAT_AT + 4) + '">' +
|
|
56
|
+
inLanguages((t) => t('ob.intro.reading', { key: '<span class="ob-key">' + INTRO_KEY + '</span>' })) + '</div></div>' +
|
|
57
|
+
row('ai', inLanguages((t) => t('ob.intro.done')), 5.2) +
|
|
58
|
+
'<p class="ob-nobody" style="--d:' + seconds(CHAT_AT + 6) + '">' + inLanguages((t) => t('ob.intro.nobody')) + '</p>' +
|
|
59
|
+
'</div></div></section>';
|
|
60
|
+
}
|
|
61
|
+
function seconds(value) {
|
|
62
|
+
return Number(value.toFixed(2)) + 's';
|
|
63
|
+
}
|
|
64
|
+
/** The intro's own look. Colours come from the tokens only. */
|
|
65
|
+
export const INTRO_STYLE = String.raw `
|
|
66
|
+
.ob-intro{position:relative;width:100%;max-width:900px;margin:0 auto;height:calc(100vh - 150px);min-height:360px}
|
|
67
|
+
.ob-intro-stage{position:absolute;inset:0;display:grid;place-items:center;animation:obOut .8s ease-in 11.5s forwards}
|
|
68
|
+
.ob-intro-files,.ob-intro-chat{grid-area:1/1;display:flex;flex-direction:column;align-items:center;gap:22px;width:100%}
|
|
69
|
+
.ob-intro-files{animation:obOut .6s ease-in 3.2s forwards}
|
|
70
|
+
.ob-intro-working{display:flex;align-items:center;gap:10px;font-size:15px;color:var(--text-2);animation:obIn .5s ease-out both}
|
|
71
|
+
.ob-intro-dot{width:8px;height:8px;border-radius:50%;background:var(--mint);animation:obTwinkle 1s ease-in-out infinite}
|
|
72
|
+
.ob-intro-type{display:inline-block;overflow:hidden;white-space:nowrap;animation:obType 1.2s steps(36) both}
|
|
73
|
+
.ob-chips{position:relative;display:flex;flex-wrap:wrap;justify-content:center;gap:10px;max-width:720px;padding:16px 20px;border-radius:18px;background:var(--card);border:1px solid var(--white-08);overflow:hidden}
|
|
74
|
+
.ob-beam{position:absolute;top:0;bottom:0;left:-10%;width:80px;opacity:0;background:linear-gradient(90deg,transparent,var(--coral-35),transparent);animation:obBeam 1.4s ease-in-out 1.3s}
|
|
75
|
+
.ob-chip{position:relative;display:inline-flex;align-items:center;gap:7px;font-size:14.5px;font-weight:500;color:var(--text);padding:6px 11px;border-radius:8px;border:1px solid var(--white-12);background:var(--panel);animation:obIn .4s ease-out var(--d) both}
|
|
76
|
+
.ob-chip-hot{animation:obIn .4s ease-out var(--d) both,obLit .4s ease var(--lit) forwards}
|
|
77
|
+
.ob-chip-icon{flex:none;width:14px;height:14px;border-radius:3px}
|
|
78
|
+
.ob-chip-file{border:1.5px solid var(--white-40)}
|
|
79
|
+
.ob-chip-folder{height:11px;border-radius:2px 4px 3px 3px;background:var(--amber-35)}
|
|
80
|
+
.ob-ping{position:absolute;right:-4px;top:-4px;width:9px;height:9px;border-radius:50%;background:var(--coral);opacity:0;animation:obPing 1.4s ease-out var(--lit) infinite}
|
|
81
|
+
.ob-intro-chat{max-width:600px;gap:16px;padding:0 20px;align-items:stretch}
|
|
82
|
+
.ob-chat-row{display:flex;gap:12px;align-items:flex-start;animation:obIn .5s ease-out var(--d) both}
|
|
83
|
+
.ob-chat-you{justify-content:flex-end;text-align:right}
|
|
84
|
+
.ob-chat-say{min-width:0}
|
|
85
|
+
.ob-chat-who{font-size:12px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--text-3);margin-bottom:6px}
|
|
86
|
+
.ob-av{flex:none;width:34px;height:34px;border-radius:50%;border:1px solid var(--white-14);background:var(--raised);color:var(--text-soft);font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center}
|
|
87
|
+
.ob-av-ai{color:var(--text-soft)}
|
|
88
|
+
.ob-bubble{display:inline-block;background:var(--raised);border:1px solid var(--white-10);border-radius:16px;padding:12px 16px;font-size:17px;line-height:1.45;color:var(--text);text-align:left}
|
|
89
|
+
.ob-hidden-part{display:flex;flex-direction:column;gap:12px;align-items:flex-start;border-radius:16px;border:1.5px dashed var(--coral-50);background:var(--coral-05);padding:16px 16px 18px;animation:obIn .6s ease-out var(--d) both}
|
|
90
|
+
.ob-hidden-head{display:flex;align-items:center;gap:9px;font-size:13.5px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--coral-text)}
|
|
91
|
+
.ob-eye{width:20px;height:20px;flex:none}
|
|
92
|
+
.ob-bubble-hot{background:var(--coral-08);border-color:var(--coral-35);animation:obIn .5s ease-out var(--d) both}
|
|
93
|
+
.ob-bubble-hot strong{color:var(--coral-text)}
|
|
94
|
+
.ob-key{font-family:var(--mono);font-size:15.5px;color:var(--coral-text);background:var(--coral-14);border:1px solid var(--coral-45);border-radius:6px;padding:1px 7px;white-space:nowrap}
|
|
95
|
+
.ob-nobody{margin:6px 0 0;text-align:center;font-size:19px;font-weight:600;color:var(--text);animation:obIn .6s ease-out var(--d) both}
|
|
96
|
+
@media (max-height:820px){.ob-intro-chat{transform:scale(.85)}}
|
|
97
|
+
@media (max-height:700px){.ob-intro-chat{transform:scale(.72)}}
|
|
98
|
+
@media (max-height:600px){.ob-intro-chat{transform:scale(.6)}}
|
|
99
|
+
@media (max-width:640px){.ob-bubble{font-size:15px}.ob-key{font-size:13.5px}.ob-chip{font-size:13px}}
|
|
100
|
+
`;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** W15's order: the hook, the names, search protection, the switches, what is said. Nothing that cannot be written. */
|
|
2
|
+
export function onboardingChanges(choices, inForce) {
|
|
3
|
+
const changes = [];
|
|
4
|
+
if (choices.watch && inForce.canWrite && inForce.watch !== choices.scope) {
|
|
5
|
+
changes.push({ change: 'watch', where: choices.scope, ...(inForce.watch === false ? {} : { from: inForce.watch }) });
|
|
6
|
+
}
|
|
7
|
+
const known = new Set(inForce.protected);
|
|
8
|
+
const fresh = (names) => [...new Set(names.map((pattern) => pattern.trim()))].filter((pattern) => pattern !== '' && !known.has(pattern));
|
|
9
|
+
// KD2: a row not watched starts on Block and has nothing to switch; Tell me puts its patterns on the told list, as an
|
|
10
|
+
// added name's are.
|
|
11
|
+
const toldRows = inForce.rows.filter((row) => !row.watched && row.mode === 'block' && choices.modes[row.key] === 'tell').flatMap((row) => row.patterns);
|
|
12
|
+
const told = inForce.canTell ? [...new Set([...fresh(choices.tell), ...toldRows])] : [];
|
|
13
|
+
const blocked = fresh(choices.protect).filter((pattern) => !told.includes(pattern));
|
|
14
|
+
// KD2: Block is what the row says once Finish is done, so a row left on it gets the rules no file holds yet. A row
|
|
15
|
+
// switched back to Block from Tell me is the switch's to write.
|
|
16
|
+
const unruled = inForce.rows.filter((row) => row.mode === 'block' && !row.ruled && (choices.modes[row.key] ?? 'block') === 'block').map((row) => row.key);
|
|
17
|
+
if ((blocked.length > 0 || unruled.length > 0) && inForce.canAdd)
|
|
18
|
+
changes.push({ change: 'protect', patterns: blocked, rows: unruled });
|
|
19
|
+
const switched = inForce.rows.flatMap((row) => {
|
|
20
|
+
const to = choices.modes[row.key];
|
|
21
|
+
return to !== undefined && to !== row.mode && row.switchable && inForce.canTell ? [{ change: 'mode', key: row.key, to }] : [];
|
|
22
|
+
});
|
|
23
|
+
// F38 as amended: Block keeps a file from searches too. An add and a switch back to Block install `refuse` themselves.
|
|
24
|
+
const staysBlocked = inForce.rows.some((row) => row.watched && (choices.modes[row.key] ?? row.mode) === 'block');
|
|
25
|
+
const installsRefuse = changes.some((change) => change.change === 'protect') || switched.some((change) => change.change === 'mode' && change.to === 'block');
|
|
26
|
+
if (inForce.refuse === false && inForce.canWrite && staysBlocked && !installsRefuse)
|
|
27
|
+
changes.push({ change: 'search', where: choices.scope });
|
|
28
|
+
changes.push(...switched);
|
|
29
|
+
if (told.length > 0)
|
|
30
|
+
changes.push({ change: 'tell', patterns: told });
|
|
31
|
+
// Rows 2 and 3 say nothing without the hook, and the page does not let them move while row 1 is off (W13).
|
|
32
|
+
if (choices.watch && inForce.noticesWritable) {
|
|
33
|
+
const on = choices.stopped === inForce.stopped ? undefined : choices.stopped ? 'refused' : 'value';
|
|
34
|
+
const clean = choices.fine === inForce.fine ? undefined : choices.fine ? 'once' : 'off';
|
|
35
|
+
if (on !== undefined || clean !== undefined) {
|
|
36
|
+
changes.push({ change: 'notices', ...(on === undefined ? {} : { on }), ...(clean === undefined ? {} : { clean }) });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return changes;
|
|
40
|
+
}
|