savepoint 1.0.3 → 1.0.6
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/package.json +10 -3
- package/{savepoint → savepoint.exe} +0 -0
- package/.claude/settings.local.json +0 -38
- package/.golangci.yml +0 -11
- package/.prettierignore +0 -4
- package/.savepoint/Design.md +0 -206
- package/.savepoint/PRD.md +0 -58
- package/.savepoint/config.yml +0 -27
- package/.savepoint/releases/v1/epics/E01-go-setup/E01-Detail.md +0 -39
- package/.savepoint/releases/v1/epics/E01-go-setup/tasks/T001-init-module.md +0 -42
- package/.savepoint/releases/v1/epics/E01-go-setup/tasks/T002-entrypoint.md +0 -23
- package/.savepoint/releases/v1/epics/E01-go-setup/tasks/T003-directory-structure.md +0 -24
- package/.savepoint/releases/v1/epics/E01-go-setup/tasks/T004-makefile.md +0 -23
- package/.savepoint/releases/v1/epics/E02-data-readers/E02-Detail.md +0 -61
- package/.savepoint/releases/v1/epics/E02-data-readers/tasks/T001-task-struct.md +0 -29
- package/.savepoint/releases/v1/epics/E02-data-readers/tasks/T002-frontmatter-parser.md +0 -30
- package/.savepoint/releases/v1/epics/E02-data-readers/tasks/T003-router-reader.md +0 -29
- package/.savepoint/releases/v1/epics/E02-data-readers/tasks/T004-config-reader.md +0 -29
- package/.savepoint/releases/v1/epics/E02-data-readers/tasks/T005-discovery.md +0 -30
- package/.savepoint/releases/v1/epics/E03-board-tui-core/E03-Detail.md +0 -38
- package/.savepoint/releases/v1/epics/E03-board-tui-core/tasks/T001-model.md +0 -29
- package/.savepoint/releases/v1/epics/E03-board-tui-core/tasks/T002-update-loop.md +0 -30
- package/.savepoint/releases/v1/epics/E03-board-tui-core/tasks/T003-view.md +0 -34
- package/.savepoint/releases/v1/epics/E03-board-tui-core/tasks/T004-styles.md +0 -29
- package/.savepoint/releases/v1/epics/E03-board-tui-core/tasks/T005-layout.md +0 -42
- package/.savepoint/releases/v1/epics/E04-board-components/E04-Detail.md +0 -44
- package/.savepoint/releases/v1/epics/E04-board-components/tasks/T001-column.md +0 -34
- package/.savepoint/releases/v1/epics/E04-board-components/tasks/T002-card.md +0 -33
- package/.savepoint/releases/v1/epics/E04-board-components/tasks/T003-epic-panel.md +0 -49
- package/.savepoint/releases/v1/epics/E04-board-components/tasks/T004-detail-overlay.md +0 -40
- package/.savepoint/releases/v1/epics/E04-board-components/tasks/T005-release-dropdown.md +0 -33
- package/.savepoint/releases/v1/epics/E04-board-components/tasks/T006-help-overlay.md +0 -34
- package/.savepoint/releases/v1/epics/E05-phase-transitions/E05-Detail.md +0 -38
- package/.savepoint/releases/v1/epics/E05-phase-transitions/tasks/T001-phase-stepping.md +0 -29
- package/.savepoint/releases/v1/epics/E05-phase-transitions/tasks/T002-gates.md +0 -31
- package/.savepoint/releases/v1/epics/E05-phase-transitions/tasks/T003-write-task.md +0 -31
- package/.savepoint/releases/v1/epics/E05-phase-transitions/tasks/T004-write-router.md +0 -31
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/E06-Detail.md +0 -62
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T001-color-system.md +0 -39
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T002-header-and-dividers.md +0 -52
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T003-footer-status-bar.md +0 -52
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T004-component-refinement.md +0 -53
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T005-restore-nav-hints.md +0 -39
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T007-detail-card-fixes.md +0 -36
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T008-checkbox-states.md +0 -40
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T009-router-priority-marker.md +0 -48
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T010-auto-refresh-watcher.md +0 -66
- package/.savepoint/releases/v1/epics/_archived/E01-archive-and-reset/Design.md +0 -39
- package/.savepoint/releases/v1/epics/_archived/E01-archive-and-reset/tasks/T001-archive-epics.md +0 -20
- package/.savepoint/releases/v1/epics/_archived/E01-archive-and-reset/tasks/T002-rewrite-prd.md +0 -22
- package/.savepoint/releases/v1/epics/_archived/E01-archive-and-reset/tasks/T003-create-epic-stubs.md +0 -24
- package/.savepoint/releases/v1/epics/_archived/E01-archive-and-reset/tasks/T004-update-router.md +0 -22
- package/.savepoint/releases/v1/epics/_archived/E01-scaffolding/Design.md +0 -118
- package/.savepoint/releases/v1/epics/_archived/E01-scaffolding/handoff.md +0 -9
- package/.savepoint/releases/v1/epics/_archived/E01-scaffolding/tasks/T001-package-baseline.md +0 -45
- package/.savepoint/releases/v1/epics/_archived/E01-scaffolding/tasks/T002-typescript-build.md +0 -48
- package/.savepoint/releases/v1/epics/_archived/E01-scaffolding/tasks/T003-vitest-smoke.md +0 -43
- package/.savepoint/releases/v1/epics/_archived/E01-scaffolding/tasks/T004-lint-format-gates.md +0 -45
- package/.savepoint/releases/v1/epics/_archived/E01-scaffolding/tasks/T005-scaffold-verification.md +0 -40
- package/.savepoint/releases/v1/epics/_archived/E02-data-model/Design.md +0 -142
- package/.savepoint/releases/v1/epics/_archived/E02-data-model/tasks/T001-domain-ids-status.md +0 -27
- package/.savepoint/releases/v1/epics/_archived/E02-data-model/tasks/T002-markdown-frontmatter-boundary.md +0 -28
- package/.savepoint/releases/v1/epics/_archived/E02-data-model/tasks/T003-task-documents.md +0 -29
- package/.savepoint/releases/v1/epics/_archived/E02-data-model/tasks/T004-release-epic-router-config-readers.md +0 -30
- package/.savepoint/releases/v1/epics/_archived/E02-data-model/tasks/T005-dependency-validation.md +0 -29
- package/.savepoint/releases/v1/epics/_archived/E02-data-model/tasks/T006-epic-task-set-reader.md +0 -29
- package/.savepoint/releases/v1/epics/_archived/E02-data-model/tasks/T007-quality-gates.md +0 -31
- package/.savepoint/releases/v1/epics/_archived/E02-domain-phase-model/Design.md +0 -40
- package/.savepoint/releases/v1/epics/_archived/E02-domain-phase-model/tasks/T001-phase-types.md +0 -27
- package/.savepoint/releases/v1/epics/_archived/E02-domain-phase-model/tasks/T002-phase-frontmatter.md +0 -25
- package/.savepoint/releases/v1/epics/_archived/E02-domain-phase-model/tasks/T003-simplify-config.md +0 -26
- package/.savepoint/releases/v1/epics/_archived/E02-domain-phase-model/tasks/T004-simplify-router-domain.md +0 -24
- package/.savepoint/releases/v1/epics/_archived/E03-cli-foundation/Design.md +0 -122
- package/.savepoint/releases/v1/epics/_archived/E03-cli-foundation/tasks/T001-argument-parser-contract.md +0 -28
- package/.savepoint/releases/v1/epics/_archived/E03-cli-foundation/tasks/T002-help-text-generation.md +0 -28
- package/.savepoint/releases/v1/epics/_archived/E03-cli-foundation/tasks/T003-terminal-environment-detection.md +0 -27
- package/.savepoint/releases/v1/epics/_archived/E03-cli-foundation/tasks/T004-command-stub-modules.md +0 -29
- package/.savepoint/releases/v1/epics/_archived/E03-cli-foundation/tasks/T005-cli-runner-dispatch.md +0 -34
- package/.savepoint/releases/v1/epics/_archived/E03-cli-foundation/tasks/T006-entrypoint-quality-gates.md +0 -32
- package/.savepoint/releases/v1/epics/_archived/E03-cli-simplify/Design.md +0 -43
- package/.savepoint/releases/v1/epics/_archived/E03-cli-simplify/tasks/T001-strip-args.md +0 -26
- package/.savepoint/releases/v1/epics/_archived/E03-cli-simplify/tasks/T002-strip-help.md +0 -23
- package/.savepoint/releases/v1/epics/_archived/E03-cli-simplify/tasks/T003-strip-run.md +0 -23
- package/.savepoint/releases/v1/epics/_archived/E03-cli-simplify/tasks/T004-delete-commands.md +0 -24
- package/.savepoint/releases/v1/epics/_archived/E03-cli-simplify/tasks/T005-update-cli-tests.md +0 -22
- package/.savepoint/releases/v1/epics/_archived/E04-board-phase-integration/Design.md +0 -48
- package/.savepoint/releases/v1/epics/_archived/E04-board-phase-integration/tasks/T001-board-data-phases.md +0 -26
- package/.savepoint/releases/v1/epics/_archived/E04-board-phase-integration/tasks/T002-phase-rendering.md +0 -28
- package/.savepoint/releases/v1/epics/_archived/E04-board-phase-integration/tasks/T003-detail-pane-phases.md +0 -27
- package/.savepoint/releases/v1/epics/_archived/E04-board-phase-integration/tasks/T004-phase-transitions.md +0 -42
- package/.savepoint/releases/v1/epics/_archived/E04-board-phase-integration/tasks/T005-phase-gates.md +0 -24
- package/.savepoint/releases/v1/epics/_archived/E04-board-phase-integration/tasks/T006-phase-write-back.md +0 -24
- package/.savepoint/releases/v1/epics/_archived/E04-board-phase-integration/tasks/T007-remove-audit-flow.md +0 -27
- package/.savepoint/releases/v1/epics/_archived/E04-board-phase-integration/tasks/T008-board-tests.md +0 -25
- package/.savepoint/releases/v1/epics/_archived/E04-templates-and-prompts/Design.md +0 -85
- package/.savepoint/releases/v1/epics/_archived/E04-templates-and-prompts/tasks/T001-project-template-assets.md +0 -17
- package/.savepoint/releases/v1/epics/_archived/E04-templates-and-prompts/tasks/T002-release-and-prompt-assets.md +0 -20
- package/.savepoint/releases/v1/epics/_archived/E04-templates-and-prompts/tasks/T003-template-registry-renderer.md +0 -22
- package/.savepoint/releases/v1/epics/_archived/E04-templates-and-prompts/tasks/T004-template-integrity-tests.md +0 -17
- package/.savepoint/releases/v1/epics/_archived/E04-templates-and-prompts/tasks/T005-template-closeout-quality-gates.md +0 -16
- package/.savepoint/releases/v1/epics/_archived/E05-init-command/Design.md +0 -88
- package/.savepoint/releases/v1/epics/_archived/E05-init-command/tasks/T001-init-cli-contract.md +0 -22
- package/.savepoint/releases/v1/epics/_archived/E05-init-command/tasks/T002-target-validation.md +0 -23
- package/.savepoint/releases/v1/epics/_archived/E05-init-command/tasks/T003-scaffold-writer.md +0 -24
- package/.savepoint/releases/v1/epics/_archived/E05-init-command/tasks/T004-magic-prompt-and-clipboard.md +0 -23
- package/.savepoint/releases/v1/epics/_archived/E05-init-command/tasks/T005-dev-deps-install-option.md +0 -24
- package/.savepoint/releases/v1/epics/_archived/E05-init-command/tasks/T006-init-command-integration.md +0 -28
- package/.savepoint/releases/v1/epics/_archived/E05-project-cleanup/Design.md +0 -53
- package/.savepoint/releases/v1/epics/_archived/E05-project-cleanup/tasks/T001-delete-dead-src.md +0 -23
- package/.savepoint/releases/v1/epics/_archived/E05-project-cleanup/tasks/T002-delete-dead-tests.md +0 -26
- package/.savepoint/releases/v1/epics/_archived/E05-project-cleanup/tasks/T003-delete-assets.md +0 -25
- package/.savepoint/releases/v1/epics/_archived/E05-project-cleanup/tasks/T004-clean-savepoint.md +0 -28
- package/.savepoint/releases/v1/epics/_archived/E05-project-cleanup/tasks/T005-rewrite-agents-md.md +0 -28
- package/.savepoint/releases/v1/epics/_archived/E05-project-cleanup/tasks/T006-clean-package-json.md +0 -23
- package/.savepoint/releases/v1/epics/_archived/E05-project-cleanup/tasks/T007-verify.md +0 -25
- package/.savepoint/releases/v1/epics/_archived/E06-tui-board/Design.md +0 -104
- package/.savepoint/releases/v1/epics/_archived/E06-tui-board/tasks/T001-board-command-data.md +0 -23
- package/.savepoint/releases/v1/epics/_archived/E06-tui-board/tasks/T002-board-view-state.md +0 -24
- package/.savepoint/releases/v1/epics/_archived/E06-tui-board/tasks/T003-transition-gates-and-writes.md +0 -25
- package/.savepoint/releases/v1/epics/_archived/E06-tui-board/tasks/T004-terminal-theme.md +0 -23
- package/.savepoint/releases/v1/epics/_archived/E06-tui-board/tasks/T005-ink-board-ui.md +0 -26
- package/.savepoint/releases/v1/epics/_archived/E06-tui-board/tasks/T006-board-integration-audit-entry.md +0 -24
- package/.savepoint/releases/v1/epics/_archived/E07-audit-pipeline/Design.md +0 -88
- package/.savepoint/releases/v1/epics/_archived/E07-audit-pipeline/tasks/T001-audit-cli-contract.md +0 -23
- package/.savepoint/releases/v1/epics/_archived/E07-audit-pipeline/tasks/T002-quality-gate-runner.md +0 -23
- package/.savepoint/releases/v1/epics/_archived/E07-audit-pipeline/tasks/T003-snapshot-and-prompt.md +0 -23
- package/.savepoint/releases/v1/epics/_archived/E07-audit-pipeline/tasks/T004-audit-orchestration-router.md +0 -27
- package/.savepoint/releases/v1/epics/_archived/E07-audit-pipeline/tasks/T005-proposal-validation-apply.md +0 -25
- package/.savepoint/releases/v1/epics/_archived/E07-audit-pipeline/tasks/T006-audit-review-state.md +0 -24
- package/.savepoint/releases/v1/epics/_archived/E07-audit-pipeline/tasks/T007-audit-review-ui.md +0 -26
- package/.savepoint/releases/v1/epics/_archived/E07-audit-pipeline/tasks/T008-audit-pipeline-integration.md +0 -24
- package/.savepoint/releases/v1/epics/_archived/E08-board-workflow-cleanup/Design.md +0 -103
- package/.savepoint/releases/v1/epics/_archived/E08-board-workflow-cleanup/tasks/T001-acceptance-criteria-model.md +0 -30
- package/.savepoint/releases/v1/epics/_archived/E08-board-workflow-cleanup/tasks/T002-release-task-set-reader.md +0 -33
- package/.savepoint/releases/v1/epics/_archived/E08-board-workflow-cleanup/tasks/T003-board-data-and-plain-output.md +0 -34
- package/.savepoint/releases/v1/epics/_archived/E08-board-workflow-cleanup/tasks/T004-board-selection-state.md +0 -33
- package/.savepoint/releases/v1/epics/_archived/E08-board-workflow-cleanup/tasks/T005-ink-board-layout-cleanup.md +0 -37
- package/.savepoint/releases/v1/epics/_archived/E08-board-workflow-cleanup/tasks/T006-task-detail-popup.md +0 -36
- package/.savepoint/releases/v1/epics/_archived/E08-board-workflow-cleanup/tasks/T007-templates-acceptance-criteria.md +0 -34
- package/.savepoint/releases/v1/epics/_archived/E08-board-workflow-cleanup/tasks/T008-board-workflow-integration.md +0 -41
- package/.savepoint/releases/v1/epics/_archived/E09-doctor-command/Design.md +0 -70
- package/.savepoint/releases/v1/epics/_archived/E10-docs-and-packaging/Design.md +0 -68
- package/.savepoint/releases/v1/epics/_archived/E11-release-validation/Design.md +0 -68
- package/.savepoint/releases/v1/v1-PRD.md +0 -66
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/E01-Detail.md +0 -40
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T001-next-activity-header.md +0 -56
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T002-rename-epic-design-files.md +0 -38
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T003-rename-release-prd.md +0 -28
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T004-update-instruction-files.md +0 -51
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T005-update-cross-references.md +0 -45
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T006-column-and-detail-scrolling.md +0 -68
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T007-column-focus-border-stability.md +0 -57
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/E02-Audit.md +0 -124
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/E02-Detail.md +0 -49
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T001-fix-makefile.md +0 -37
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T002-linux-build-target.md +0 -38
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T003-macos-build-target.md +0 -36
- package/.savepoint/releases/v1.1/epics/E02-cross-platform-compatibility/tasks/T004-smoke-tests-and-artifacts.md +0 -59
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/E03-Audit.md +0 -195
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/E03-Detail.md +0 -45
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/tasks/T001-border-resize-fix.md +0 -40
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/tasks/T002-next-activity-below-header.md +0 -64
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/tasks/T003-checkbox-rendering-fix.md +0 -56
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/tasks/T005-unify-status-glyphs.md +0 -65
- package/.savepoint/releases/v1.1/epics/E03-ui-visual-refinement/tasks/T006-forced-256-color-profile.md +0 -36
- package/.savepoint/releases/v1.1/epics/E04-epic-navigation/E04-Audit.md +0 -167
- package/.savepoint/releases/v1.1/epics/E04-epic-navigation/E04-Detail.md +0 -51
- package/.savepoint/releases/v1.1/epics/E04-epic-navigation/tasks/T001-sidebar-focusable-navigation.md +0 -65
- package/.savepoint/releases/v1.1/epics/E04-epic-navigation/tasks/T002-epic-detail-overlay.md +0 -73
- package/.savepoint/releases/v1.1/epics/E04-epic-navigation/tasks/T003-epic-status-glyphs.md +0 -73
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/E05-Audit.md +0 -237
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/E05-Detail.md +0 -54
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T001-update-agents-md.md +0 -45
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T002-update-router-md.md +0 -40
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T003-update-design-md.md +0 -47
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T004-implement-m-hotkey.md +0 -98
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T005-update-help-overlay.md +0 -33
- package/.savepoint/releases/v1.1/epics/E05-tasking-permissions/tasks/T006-tests-and-quality-gates.md +0 -62
- package/.savepoint/releases/v1.1/epics/E06-audit-command/E06-Audit.md +0 -56
- package/.savepoint/releases/v1.1/epics/E06-audit-command/E06-Detail.md +0 -63
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T005-proposals.md +0 -44
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T007-apply-close.md +0 -35
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T009-integration.md +0 -40
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T010-audit-file-migration.md +0 -45
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T011-model-tab-state.md +0 -26
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T012-epic-audit-render.md +0 -33
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T013-handle-tab-keys.md +0 -34
- package/.savepoint/releases/v1.1/epics/E06-audit-command/tasks/T014-tab-indicator.md +0 -33
- package/.savepoint/releases/v1.1/epics/E07-init-command/E07-Audit.md +0 -336
- package/.savepoint/releases/v1.1/epics/E07-init-command/E07-Detail.md +0 -61
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T001-cli-entrypoint.md +0 -37
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T002-target-validation.md +0 -28
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T003-scaffold-writer.md +0 -46
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T004-atomic-writes.md +0 -27
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T005-magic-prompt.md +0 -25
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T006-clipboard.md +0 -26
- package/.savepoint/releases/v1.1/epics/E07-init-command/tasks/T007-integration-test.md +0 -26
- package/.savepoint/releases/v1.1/epics/E08-board-command/E08-Audit.md +0 -333
- package/.savepoint/releases/v1.1/epics/E08-board-command/E08-Detail.md +0 -68
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T001-cli-entrypoint.md +0 -26
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T002-non-tty-fallback.md +0 -27
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T003-tui-app-shell.md +0 -28
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T004-board-model.md +0 -29
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T005-detail-pane.md +0 -27
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T006-status-transitions.md +0 -29
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T007-theme-fallbacks.md +0 -29
- package/.savepoint/releases/v1.1/epics/E08-board-command/tasks/T008-integration-test.md +0 -27
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/E09-Audit.md +0 -207
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/E09-Detail.md +0 -65
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T001-cli-entrypoint.md +0 -24
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T002-config-router-validation.md +0 -28
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T003-structure-checks.md +0 -29
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T004-dependency-checks.md +0 -27
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T005-audit-orphan-checks.md +0 -28
- package/.savepoint/releases/v1.1/epics/E09-doctor-command/tasks/T006-quality-gates-report.md +0 -31
- package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/E11-Detail.md +0 -36
- package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T001-debug-logging.md +0 -25
- package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T002-increase-debounce.md +0 -21
- package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T003-error-handling.md +0 -22
- package/.savepoint/releases/v1.1/epics/E11-board-refresh-fix/tasks/T004-test-verify.md +0 -29
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/E12-Audit.md +0 -444
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/E12-Detail.md +0 -45
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T001-default-phase.md +0 -35
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T002-default-status.md +0 -19
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T003-better-errors.md +0 -29
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T004-validate-on-write.md +0 -25
- package/.savepoint/releases/v1.1/epics/E12-validation-fix/tasks/T005-tests.md +0 -37
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/E13-Audit.md +0 -118
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/E13-Detail.md +0 -73
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T001-safe-cleanup.md +0 -66
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T002-bug-fixes.md +0 -35
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T003-centralize-duplication.md +0 -60
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T004-infrastructure.md +0 -33
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T005-decompose-update.md +0 -37
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T006-async-io.md +0 -40
- package/.savepoint/releases/v1.1/epics/E13-audit-remediation/tasks/T007-test-coverage.md +0 -37
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/E14-Audit.md +0 -267
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/E14-Detail.md +0 -54
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T001-group-model.md +0 -39
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T002-data-interfaces.md +0 -42
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T003-discover-orphans.md +0 -33
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T004-epic-panel-headings.md +0 -35
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T005-shell-tokenization.md +0 -27
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T006-unify-enums.md +0 -29
- package/.savepoint/releases/v1.1/epics/E14-structural-improvements/tasks/T007-testutil-package.md +0 -28
- package/.savepoint/releases/v1.1/epics/E15-hardening/E15-Detail.md +0 -43
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T001-benchmarks.md +0 -31
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T002-fuzz-targets.md +0 -28
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T003-debug-flag.md +0 -30
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T004-dist-checksums.md +0 -27
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T005-windows-targets.md +0 -28
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T006-abbreviation-splitting.md +0 -26
- package/.savepoint/releases/v1.1/epics/E15-hardening/tasks/T007-root-test-allowlist.md +0 -28
- package/.savepoint/releases/v1.1/epics/_archived/T001-cli-entrypoint.md +0 -25
- package/.savepoint/releases/v1.1/epics/_archived/T002-quality-gates.md +0 -27
- package/.savepoint/releases/v1.1/epics/_archived/T003-snapshot.md +0 -27
- package/.savepoint/releases/v1.1/epics/_archived/T004-ai-reconcile.md +0 -29
- package/.savepoint/releases/v1.1/epics/_archived/T006-tui-review.md +0 -31
- package/.savepoint/releases/v1.1/epics/_archived/T008-skip-handling.md +0 -34
- package/.savepoint/releases/v1.1/v1.1-PRD.md +0 -139
- package/.savepoint/router.md +0 -57
- package/.savepoint/visual-identity.md +0 -125
- package/AGENTS.md +0 -99
- package/CLAUDE.md +0 -1
- package/GEMINI.md +0 -1
- package/Makefile +0 -29
- package/agent-skills/ink-tui-design/SKILL.md +0 -309
- package/agent-skills/ink-tui-design/references/component-patterns.md +0 -371
- package/agent-skills/ink-tui-design/references/hooks-guide.md +0 -436
- package/agent-skills/ink-tui-design/references/ink-gotchas.md +0 -330
- package/agent-skills/ink-tui-design/references/testing-patterns.md +0 -384
- package/agent-skills/savepoint-audit/SKILL.md +0 -87
- package/agent-skills/savepoint-build-task/SKILL.md +0 -44
- package/agent-skills/savepoint-create-plan/SKILL.md +0 -33
- package/agent-skills/savepoint-create-task/SKILL.md +0 -44
- package/agent-skills/savepoint-draft-prd/SKILL.md +0 -37
- package/agent-skills/savepoint-system-design/SKILL.md +0 -38
- package/agent-skills/superpowers/brainstorming/SKILL.md +0 -165
- package/agent-skills/superpowers/brainstorming/visual-companion.md +0 -304
- package/agent-skills/superpowers/dispatching-parallel-agents/SKILL.md +0 -193
- package/agent-skills/superpowers/executing-plans/SKILL.md +0 -77
- package/agent-skills/superpowers/finishing-a-development-branch/SKILL.md +0 -213
- package/agent-skills/superpowers/receiving-code-review/SKILL.md +0 -226
- package/agent-skills/superpowers/requesting-code-review/SKILL.md +0 -115
- package/agent-skills/superpowers/requesting-code-review/code-reviewer.md +0 -160
- package/agent-skills/superpowers/subagent-driven-development/SKILL.md +0 -292
- package/agent-skills/superpowers/subagent-driven-development/code-quality-reviewer-prompt.md +0 -27
- package/agent-skills/superpowers/subagent-driven-development/implementer-prompt.md +0 -113
- package/agent-skills/superpowers/subagent-driven-development/spec-reviewer-prompt.md +0 -61
- package/agent-skills/superpowers/systematic-debugging/SKILL.md +0 -305
- package/agent-skills/superpowers/systematic-debugging/condition-based-waiting.md +0 -122
- package/agent-skills/superpowers/systematic-debugging/defense-in-depth.md +0 -130
- package/agent-skills/superpowers/systematic-debugging/root-cause-tracing.md +0 -183
- package/agent-skills/superpowers/test-driven-development/SKILL.md +0 -389
- package/agent-skills/superpowers/test-driven-development/testing-anti-patterns.md +0 -317
- package/agent-skills/superpowers/verification-before-completion/SKILL.md +0 -147
- package/agent-skills/superpowers/writing-plans/SKILL.md +0 -159
- package/agent-skills/superpowers/writing-plans/plan-document-reviewer-prompt.md +0 -49
- package/agent_skills_test.go +0 -91
- package/assets/banner.png +0 -0
- package/assets/logo.png +0 -0
- package/assets/strawman.png +0 -0
- package/cmd/board.go +0 -59
- package/cmd/board_test.go +0 -137
- package/cmd/doctor.go +0 -53
- package/cmd/doctor_test.go +0 -146
- package/cmd/init.go +0 -63
- package/cmd/init_test.go +0 -104
- package/go.mod +0 -36
- package/go.sum +0 -75
- package/internal/board/board.go +0 -177
- package/internal/board/board_test.go +0 -168
- package/internal/board/card.go +0 -129
- package/internal/board/card_test.go +0 -254
- package/internal/board/column.go +0 -127
- package/internal/board/column_test.go +0 -139
- package/internal/board/detail.go +0 -185
- package/internal/board/detail_test.go +0 -340
- package/internal/board/epic_panel.go +0 -262
- package/internal/board/epic_panel_test.go +0 -869
- package/internal/board/help.go +0 -41
- package/internal/board/help_test.go +0 -86
- package/internal/board/integration_test.go +0 -266
- package/internal/board/interfaces.go +0 -65
- package/internal/board/interfaces_test.go +0 -114
- package/internal/board/io.go +0 -93
- package/internal/board/layout.go +0 -68
- package/internal/board/layout_test.go +0 -106
- package/internal/board/model.go +0 -235
- package/internal/board/model_test.go +0 -67
- package/internal/board/plain.go +0 -88
- package/internal/board/plain_test.go +0 -117
- package/internal/board/release.go +0 -34
- package/internal/board/release_test.go +0 -177
- package/internal/board/render_policy_test.go +0 -77
- package/internal/board/status.go +0 -23
- package/internal/board/theme.go +0 -24
- package/internal/board/theme_test.go +0 -31
- package/internal/board/transitions.go +0 -113
- package/internal/board/transitions_test.go +0 -164
- package/internal/board/tui.go +0 -32
- package/internal/board/update.go +0 -556
- package/internal/board/update_test.go +0 -575
- package/internal/board/util.go +0 -76
- package/internal/board/view.go +0 -317
- package/internal/board/view_test.go +0 -315
- package/internal/board/watch.go +0 -130
- package/internal/buildtool/main.go +0 -211
- package/internal/buildtool/main_test.go +0 -46
- package/internal/data/config.go +0 -101
- package/internal/data/config_test.go +0 -122
- package/internal/data/discover.go +0 -178
- package/internal/data/discover_test.go +0 -130
- package/internal/data/errors.go +0 -13
- package/internal/data/lifecycle.go +0 -44
- package/internal/data/lifecycle_test.go +0 -41
- package/internal/data/parser.go +0 -242
- package/internal/data/parser_test.go +0 -281
- package/internal/data/router.go +0 -52
- package/internal/data/router_test.go +0 -35
- package/internal/data/task.go +0 -57
- package/internal/data/task_test.go +0 -51
- package/internal/data/write.go +0 -218
- package/internal/data/write_test.go +0 -623
- package/internal/doctor/checks.go +0 -567
- package/internal/doctor/checks_test.go +0 -716
- package/internal/doctor/gates.go +0 -193
- package/internal/doctor/gates_test.go +0 -166
- package/internal/doctor/interfaces.go +0 -64
- package/internal/doctor/interfaces_test.go +0 -104
- package/internal/doctor/repairs.go +0 -80
- package/internal/doctor/repairs_test.go +0 -81
- package/internal/doctor/report.go +0 -157
- package/internal/doctor/report_test.go +0 -89
- package/internal/init/clipboard.go +0 -146
- package/internal/init/clipboard_test.go +0 -74
- package/internal/init/install.go +0 -16
- package/internal/init/integration_test.go +0 -197
- package/internal/init/prompt.go +0 -14
- package/internal/init/prompt_test.go +0 -77
- package/internal/init/scaffold.go +0 -59
- package/internal/init/scaffold_test.go +0 -179
- package/internal/init/template_freshness_test.go +0 -56
- package/internal/init/validate.go +0 -85
- package/internal/init/validate_test.go +0 -141
- package/internal/init/write.go +0 -73
- package/internal/init/write_test.go +0 -91
- package/internal/styles/palette.go +0 -49
- package/internal/styles/styles.go +0 -139
- package/internal/styles/styles_test.go +0 -133
- package/internal/testutil/fixture.go +0 -113
- package/internal/testutil/fs.go +0 -26
- package/main.go +0 -117
- package/project-audit/audit_report_glm_5.1.md +0 -411
- package/project-audit/audit_report_opus_4.6 +0 -406
- package/project-audit/consolidated-audit-report.md +0 -456
- package/scripts/vitest-preload.cjs +0 -95
- package/templates/project/.savepoint/Design.md +0 -47
- package/templates/project/.savepoint/PRD.md +0 -34
- package/templates/project/.savepoint/config.yml +0 -27
- package/templates/project/.savepoint/router.md +0 -153
- package/templates/project/.savepoint/visual-identity.md +0 -122
- package/templates/project/AGENTS.md +0 -88
- package/templates/project/agent-skills/savepoint-audit/SKILL.md +0 -87
- package/templates/project/agent-skills/savepoint-build-task/SKILL.md +0 -44
- package/templates/project/agent-skills/savepoint-create-plan/SKILL.md +0 -33
- package/templates/project/agent-skills/savepoint-create-task/SKILL.md +0 -44
- package/templates/project/agent-skills/savepoint-draft-prd/SKILL.md +0 -37
- package/templates/project/agent-skills/savepoint-system-design/SKILL.md +0 -38
- package/templates/prompts/audit-reconciliation.prompt.md +0 -72
- package/templates/prompts/design.prompt.md +0 -45
- package/templates/prompts/epic-design.prompt.md +0 -43
- package/templates/prompts/magic-prompt.prompt.md +0 -7
- package/templates/prompts/prd.prompt.md +0 -42
- package/templates/prompts/task-breakdown.prompt.md +0 -54
- package/templates/prompts/task-building.prompt.md +0 -38
- package/templates/prompts/task-planning.prompt.md +0 -53
- package/templates/release/v1/PRD.md +0 -37
|
@@ -1,869 +0,0 @@
|
|
|
1
|
-
package board
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"os"
|
|
5
|
-
"path/filepath"
|
|
6
|
-
"strings"
|
|
7
|
-
"testing"
|
|
8
|
-
|
|
9
|
-
tea "github.com/charmbracelet/bubbletea"
|
|
10
|
-
"github.com/opencode/savepoint/internal/data"
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
func TestRenderEpicSidebar_containsEpicsHeader(t *testing.T) {
|
|
14
|
-
got := RenderEpicSidebar([]string{"E01", "E02"}, "E01", 28, false, 0, nil, 999)
|
|
15
|
-
if !strings.Contains(got, "EPICS") {
|
|
16
|
-
t.Error("RenderEpicSidebar missing EPICS header")
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
func TestRenderEpicSidebar_activeEpicMarked(t *testing.T) {
|
|
21
|
-
got := RenderEpicSidebar([]string{"E01", "E02"}, "E01", 28, false, 0, nil, 999)
|
|
22
|
-
if !strings.Contains(got, epicActiveMarker) {
|
|
23
|
-
t.Errorf("RenderEpicSidebar missing active marker %q", epicActiveMarker)
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
func TestRenderEpicSidebar_focusedCursorMarked(t *testing.T) {
|
|
28
|
-
got := RenderEpicSidebar([]string{"E01", "E02"}, "E01", 28, true, 1, nil, 999)
|
|
29
|
-
if !strings.Contains(got, epicActiveMarker+" E02") {
|
|
30
|
-
t.Errorf("RenderEpicSidebar focused cursor missing marker, got %q", got)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
func TestRenderEpicSidebar_allEpicsPresent(t *testing.T) {
|
|
35
|
-
epics := []string{"E01-foo", "E02-bar", "E03-baz"}
|
|
36
|
-
got := RenderEpicSidebar(epics, "E01-foo", 32, false, 0, nil, 999)
|
|
37
|
-
for _, e := range epics {
|
|
38
|
-
if !strings.Contains(got, e) {
|
|
39
|
-
t.Errorf("RenderEpicSidebar missing epic %q", e)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
func TestRenderEpicSidebar_emptyEpicsFallback(t *testing.T) {
|
|
45
|
-
got := RenderEpicSidebar(nil, "E03", 28, false, 0, nil, 999)
|
|
46
|
-
if !strings.Contains(got, "E03") {
|
|
47
|
-
t.Error("RenderEpicSidebar with empty list should show selected epic")
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
func TestRenderEpicSidebar_emptyBothShowsNone(t *testing.T) {
|
|
52
|
-
got := RenderEpicSidebar(nil, "", 28, false, 0, nil, 999)
|
|
53
|
-
if !strings.Contains(got, "(none)") {
|
|
54
|
-
t.Error("RenderEpicSidebar with no epics and no selected should show (none)")
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
func TestRenderEpicDropdown_containsHeader(t *testing.T) {
|
|
59
|
-
got := RenderEpicDropdown([]string{"E01", "E02"}, 0, 32)
|
|
60
|
-
if !strings.Contains(got, "SELECT EPIC") {
|
|
61
|
-
t.Error("RenderEpicDropdown missing SELECT EPIC header")
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
func TestRenderEpicDropdown_cursorMarked(t *testing.T) {
|
|
66
|
-
got := RenderEpicDropdown([]string{"E01", "E02"}, 1, 32)
|
|
67
|
-
if !strings.Contains(got, epicActiveMarker) {
|
|
68
|
-
t.Errorf("RenderEpicDropdown missing cursor marker %q", epicActiveMarker)
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
func TestRenderEpicDropdown_containsHint(t *testing.T) {
|
|
73
|
-
got := RenderEpicDropdown([]string{"E01"}, 0, 32)
|
|
74
|
-
if !strings.Contains(got, "esc") {
|
|
75
|
-
t.Error("RenderEpicDropdown missing esc hint")
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
func TestRenderEpicDropdown_emptyShowsNone(t *testing.T) {
|
|
80
|
-
got := RenderEpicDropdown(nil, 0, 32)
|
|
81
|
-
if !strings.Contains(got, "(none)") {
|
|
82
|
-
t.Error("RenderEpicDropdown with no epics should show (none)")
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
func TestSliceIndex_found(t *testing.T) {
|
|
87
|
-
epics := []string{"E01", "E02", "E03"}
|
|
88
|
-
if got := sliceIndex(epics, "E02"); got != 1 {
|
|
89
|
-
t.Errorf("sliceIndex = %d, want 1", got)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
func TestSliceIndex_notFound(t *testing.T) {
|
|
94
|
-
if got := sliceIndex([]string{"E01"}, "E99"); got != 0 {
|
|
95
|
-
t.Errorf("sliceIndex not-found = %d, want 0", got)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
func TestSliceIndex_empty(t *testing.T) {
|
|
100
|
-
if got := sliceIndex(nil, "E01"); got != 0 {
|
|
101
|
-
t.Errorf("sliceIndex empty = %d, want 0", got)
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Update integration tests for epic dropdown
|
|
106
|
-
|
|
107
|
-
func TestUpdate_eKeyOpensDropdownNarrow(t *testing.T) {
|
|
108
|
-
m := NewModel(nil, "v1", "E03")
|
|
109
|
-
m.Width = 80 // narrow: < 120
|
|
110
|
-
m.Epics = []string{"E01", "E03"}
|
|
111
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("e")})
|
|
112
|
-
updated := requireModel(t, got)
|
|
113
|
-
if updated.Overlay != OverlayEpic {
|
|
114
|
-
t.Errorf("Overlay = %q, want %q", updated.Overlay, OverlayEpic)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
func TestUpdate_eKeyOpensDropdownWide(t *testing.T) {
|
|
119
|
-
m := NewModel(nil, "v1", "E03")
|
|
120
|
-
m.Width = 120
|
|
121
|
-
m.Epics = []string{"E01", "E03"}
|
|
122
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("e")})
|
|
123
|
-
updated := requireModel(t, got)
|
|
124
|
-
if updated.Overlay != OverlayEpic {
|
|
125
|
-
t.Errorf("Overlay = %q, want %q", updated.Overlay, OverlayEpic)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
func TestUpdate_epicDropdownEscCloses(t *testing.T) {
|
|
130
|
-
m := NewModel(nil, "v1", "E03")
|
|
131
|
-
m.Overlay = OverlayEpic
|
|
132
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyEsc})
|
|
133
|
-
updated := requireModel(t, got)
|
|
134
|
-
if updated.Overlay != OverlayNone {
|
|
135
|
-
t.Errorf("Overlay = %q after esc, want none", updated.Overlay)
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
func TestUpdate_epicDropdownDownMovesCursor(t *testing.T) {
|
|
140
|
-
m := NewModel(nil, "v1", "E01")
|
|
141
|
-
m.Overlay = OverlayEpic
|
|
142
|
-
m.Epics = []string{"E01", "E02", "E03"}
|
|
143
|
-
m.EpicCursor = 0
|
|
144
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyDown})
|
|
145
|
-
updated := requireModel(t, got)
|
|
146
|
-
if updated.EpicCursor != 1 {
|
|
147
|
-
t.Errorf("EpicCursor = %d, want 1", updated.EpicCursor)
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
func TestUpdate_epicDropdownUpMovesCursor(t *testing.T) {
|
|
152
|
-
m := NewModel(nil, "v1", "E02")
|
|
153
|
-
m.Overlay = OverlayEpic
|
|
154
|
-
m.Epics = []string{"E01", "E02", "E03"}
|
|
155
|
-
m.EpicCursor = 2
|
|
156
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyUp})
|
|
157
|
-
updated := requireModel(t, got)
|
|
158
|
-
if updated.EpicCursor != 1 {
|
|
159
|
-
t.Errorf("EpicCursor = %d, want 1", updated.EpicCursor)
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
func TestUpdate_epicDropdownEnterSelectsEpic(t *testing.T) {
|
|
164
|
-
tasks := []data.Task{
|
|
165
|
-
{ID: "T1", Epic: "E01", Release: "v1", Column: data.ColumnPlanned},
|
|
166
|
-
{ID: "T3", Epic: "E03", Release: "v1", Column: data.ColumnPlanned},
|
|
167
|
-
}
|
|
168
|
-
m := NewModel(tasks, "v1", "E01")
|
|
169
|
-
m.Overlay = OverlayEpic
|
|
170
|
-
m.Epics = []string{"E01", "E02", "E03"}
|
|
171
|
-
m.EpicCursor = 2
|
|
172
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyEnter})
|
|
173
|
-
updated := requireModel(t, got)
|
|
174
|
-
if updated.SelectedEpic != "E03" {
|
|
175
|
-
t.Errorf("SelectedEpic = %q, want %q", updated.SelectedEpic, "E03")
|
|
176
|
-
}
|
|
177
|
-
if updated.Overlay != OverlayNone {
|
|
178
|
-
t.Errorf("Overlay = %q after enter, want none", updated.Overlay)
|
|
179
|
-
}
|
|
180
|
-
if got := len(updated.Tasks[data.ColumnPlanned]); got != 1 {
|
|
181
|
-
t.Errorf("planned task count = %d, want 1 after epic selection", got)
|
|
182
|
-
}
|
|
183
|
-
if updated.Tasks[data.ColumnPlanned][0].ID != "T3" {
|
|
184
|
-
t.Errorf("visible task = %q, want T3", updated.Tasks[data.ColumnPlanned][0].ID)
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
func TestUpdate_epicDropdownDownClampedAtEnd(t *testing.T) {
|
|
189
|
-
m := NewModel(nil, "v1", "E03")
|
|
190
|
-
m.Overlay = OverlayEpic
|
|
191
|
-
m.Epics = []string{"E01", "E02"}
|
|
192
|
-
m.EpicCursor = 1
|
|
193
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyDown})
|
|
194
|
-
updated := requireModel(t, got)
|
|
195
|
-
if updated.EpicCursor != 1 {
|
|
196
|
-
t.Errorf("EpicCursor = %d, want 1 (clamped)", updated.EpicCursor)
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
func TestUpdate_epicDropdownUpClampedAtStart(t *testing.T) {
|
|
201
|
-
m := NewModel(nil, "v1", "E01")
|
|
202
|
-
m.Overlay = OverlayEpic
|
|
203
|
-
m.Epics = []string{"E01", "E02"}
|
|
204
|
-
m.EpicCursor = 0
|
|
205
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyUp})
|
|
206
|
-
updated := requireModel(t, got)
|
|
207
|
-
if updated.EpicCursor != 0 {
|
|
208
|
-
t.Errorf("EpicCursor = %d, want 0 (clamped)", updated.EpicCursor)
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
func TestUpdate_overlayBlocksColumnNav(t *testing.T) {
|
|
213
|
-
m := NewModel(nil, "v1", "E01")
|
|
214
|
-
m.Overlay = OverlayEpic
|
|
215
|
-
m.FocusedColumn = "planned"
|
|
216
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("l")})
|
|
217
|
-
updated := requireModel(t, got)
|
|
218
|
-
if updated.FocusedColumn != "planned" {
|
|
219
|
-
t.Error("column nav should be blocked when overlay is open")
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
func TestUpdate_leftFromPlannedFocusesEpicPanelWide(t *testing.T) {
|
|
224
|
-
m := NewModel(nil, "v1", "E02")
|
|
225
|
-
m.Width = 120
|
|
226
|
-
m.Epics = []string{"E01", "E02", "E03"}
|
|
227
|
-
|
|
228
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("h")})
|
|
229
|
-
updated := requireModel(t, got)
|
|
230
|
-
|
|
231
|
-
if !updated.EpicPanelFocus {
|
|
232
|
-
t.Fatal("EpicPanelFocus = false, want true")
|
|
233
|
-
}
|
|
234
|
-
if updated.EpicPanelCursor != 1 {
|
|
235
|
-
t.Errorf("EpicPanelCursor = %d, want 1", updated.EpicPanelCursor)
|
|
236
|
-
}
|
|
237
|
-
if updated.FocusedColumn != data.ColumnPlanned {
|
|
238
|
-
t.Errorf("FocusedColumn = %q, want planned", updated.FocusedColumn)
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
func TestUpdate_leftFromPlannedDoesNotFocusEpicPanelNarrow(t *testing.T) {
|
|
243
|
-
m := NewModel(nil, "v1", "E02")
|
|
244
|
-
m.Width = 100
|
|
245
|
-
m.Epics = []string{"E01", "E02"}
|
|
246
|
-
|
|
247
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("h")})
|
|
248
|
-
updated := requireModel(t, got)
|
|
249
|
-
|
|
250
|
-
if updated.EpicPanelFocus {
|
|
251
|
-
t.Fatal("EpicPanelFocus = true, want false")
|
|
252
|
-
}
|
|
253
|
-
if updated.FocusedColumn != data.ColumnDone {
|
|
254
|
-
t.Errorf("FocusedColumn = %q, want done", updated.FocusedColumn)
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
func TestUpdate_leftFromPlannedDoesNotFocusEmptyEpicPanel(t *testing.T) {
|
|
259
|
-
m := NewModel(nil, "v1", "")
|
|
260
|
-
m.Width = 120
|
|
261
|
-
|
|
262
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("h")})
|
|
263
|
-
updated := requireModel(t, got)
|
|
264
|
-
|
|
265
|
-
if updated.EpicPanelFocus {
|
|
266
|
-
t.Fatal("EpicPanelFocus = true, want false with no epics")
|
|
267
|
-
}
|
|
268
|
-
if updated.EpicPanelCursor != 0 {
|
|
269
|
-
t.Errorf("EpicPanelCursor = %d, want 0", updated.EpicPanelCursor)
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
func TestUpdate_windowResizeClearsEpicPanelFocusWhenHidden(t *testing.T) {
|
|
274
|
-
m := NewModel(nil, "v1", "E01")
|
|
275
|
-
m.Width = 120
|
|
276
|
-
m.Epics = []string{"E01"}
|
|
277
|
-
m.EpicPanelFocus = true
|
|
278
|
-
|
|
279
|
-
got, _ := m.Update(tea.WindowSizeMsg{Width: 100, Height: 24})
|
|
280
|
-
updated := requireModel(t, got)
|
|
281
|
-
|
|
282
|
-
if updated.EpicPanelFocus {
|
|
283
|
-
t.Fatal("EpicPanelFocus = true, want false when panel is hidden")
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
func TestUpdate_epicPanelDownUpClamped(t *testing.T) {
|
|
288
|
-
m := NewModel(nil, "v1", "E01")
|
|
289
|
-
m.Width = 120
|
|
290
|
-
m.EpicPanelFocus = true
|
|
291
|
-
m.Epics = []string{"E01", "E02"}
|
|
292
|
-
|
|
293
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyDown})
|
|
294
|
-
updated := requireModel(t, got)
|
|
295
|
-
if updated.EpicPanelCursor != 1 {
|
|
296
|
-
t.Errorf("EpicPanelCursor after down = %d, want 1", updated.EpicPanelCursor)
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
got, _ = updated.Update(tea.KeyMsg{Type: tea.KeyDown})
|
|
300
|
-
updated = requireModel(t, got)
|
|
301
|
-
if updated.EpicPanelCursor != 1 {
|
|
302
|
-
t.Errorf("EpicPanelCursor after clamped down = %d, want 1", updated.EpicPanelCursor)
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
got, _ = updated.Update(tea.KeyMsg{Type: tea.KeyUp})
|
|
306
|
-
updated = requireModel(t, got)
|
|
307
|
-
if updated.EpicPanelCursor != 0 {
|
|
308
|
-
t.Errorf("EpicPanelCursor after up = %d, want 0", updated.EpicPanelCursor)
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
func TestUpdate_epicPanelEnterOpensDetailOverlay(t *testing.T) {
|
|
313
|
-
tasks := []data.Task{
|
|
314
|
-
{ID: "T1", Epic: "E01", Release: "v1", Column: data.ColumnPlanned},
|
|
315
|
-
{ID: "T2", Epic: "E02", Release: "v1", Column: data.ColumnPlanned},
|
|
316
|
-
}
|
|
317
|
-
m := NewModel(tasks, "v1", "E01")
|
|
318
|
-
m.Width = 120
|
|
319
|
-
m.Epics = []string{"E01", "E02"}
|
|
320
|
-
m.EpicPanelFocus = true
|
|
321
|
-
m.EpicPanelCursor = 1
|
|
322
|
-
m.FocusedTask = 3
|
|
323
|
-
m.DetailOffset = 2
|
|
324
|
-
|
|
325
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyEnter})
|
|
326
|
-
updated := requireModel(t, got)
|
|
327
|
-
|
|
328
|
-
if updated.Overlay != OverlayEpicDetail {
|
|
329
|
-
t.Errorf("Overlay = %q, want %q", updated.Overlay, OverlayEpicDetail)
|
|
330
|
-
}
|
|
331
|
-
if updated.EpicDetailOffset != 0 {
|
|
332
|
-
t.Errorf("EpicDetailOffset = %d, want 0", updated.EpicDetailOffset)
|
|
333
|
-
}
|
|
334
|
-
if !updated.EpicPanelFocus {
|
|
335
|
-
t.Error("EpicPanelFocus should remain true after enter")
|
|
336
|
-
}
|
|
337
|
-
// SelectedEpic unchanged; Enter now opens detail, not selects
|
|
338
|
-
if updated.SelectedEpic != "E01" {
|
|
339
|
-
t.Errorf("SelectedEpic = %q, want E01 (unchanged)", updated.SelectedEpic)
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
func TestUpdate_epicPanelRightReturnsToPlanned(t *testing.T) {
|
|
344
|
-
m := NewModel(nil, "v1", "E01")
|
|
345
|
-
m.Width = 120
|
|
346
|
-
m.EpicPanelFocus = true
|
|
347
|
-
m.Epics = []string{"E01"}
|
|
348
|
-
m.FocusedColumn = data.ColumnDone
|
|
349
|
-
m.FocusedTask = 2
|
|
350
|
-
|
|
351
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("l")})
|
|
352
|
-
updated := requireModel(t, got)
|
|
353
|
-
|
|
354
|
-
if updated.EpicPanelFocus {
|
|
355
|
-
t.Fatal("EpicPanelFocus = true, want false")
|
|
356
|
-
}
|
|
357
|
-
if updated.FocusedColumn != data.ColumnPlanned {
|
|
358
|
-
t.Errorf("FocusedColumn = %q, want planned", updated.FocusedColumn)
|
|
359
|
-
}
|
|
360
|
-
if updated.FocusedTask != 0 {
|
|
361
|
-
t.Errorf("FocusedTask = %d, want 0", updated.FocusedTask)
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
func TestUpdate_overlayBlocksEpicPanelNav(t *testing.T) {
|
|
366
|
-
m := NewModel(nil, "v1", "E01")
|
|
367
|
-
m.Width = 120
|
|
368
|
-
m.EpicPanelFocus = true
|
|
369
|
-
m.Epics = []string{"E01", "E02"}
|
|
370
|
-
m.Overlay = OverlayHelp
|
|
371
|
-
|
|
372
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyDown})
|
|
373
|
-
updated := requireModel(t, got)
|
|
374
|
-
|
|
375
|
-
if updated.EpicPanelCursor != 0 {
|
|
376
|
-
t.Errorf("EpicPanelCursor = %d, want 0 while overlay open", updated.EpicPanelCursor)
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
func TestUpdate_epicPanelFocusAllowsGlobalQuit(t *testing.T) {
|
|
381
|
-
m := NewModel(nil, "v1", "E01")
|
|
382
|
-
m.Width = 120
|
|
383
|
-
m.EpicPanelFocus = true
|
|
384
|
-
m.Epics = []string{"E01"}
|
|
385
|
-
|
|
386
|
-
_, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("q")})
|
|
387
|
-
if cmd == nil {
|
|
388
|
-
t.Fatal("expected tea.Quit cmd from q while epic panel focused, got nil")
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
func TestUpdate_epicPanelFocusAllowsEpicDropdown(t *testing.T) {
|
|
393
|
-
m := NewModel(nil, "v1", "E02")
|
|
394
|
-
m.Width = 120
|
|
395
|
-
m.EpicPanelFocus = true
|
|
396
|
-
m.Epics = []string{"E01", "E02"}
|
|
397
|
-
|
|
398
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("e")})
|
|
399
|
-
updated := requireModel(t, got)
|
|
400
|
-
|
|
401
|
-
if updated.Overlay != OverlayEpic {
|
|
402
|
-
t.Errorf("Overlay = %q, want %q", updated.Overlay, OverlayEpic)
|
|
403
|
-
}
|
|
404
|
-
if updated.EpicCursor != 1 {
|
|
405
|
-
t.Errorf("EpicCursor = %d, want 1", updated.EpicCursor)
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
func TestUpdate_epicPanelFocusAllowsReleaseDropdown(t *testing.T) {
|
|
410
|
-
m := NewModel(nil, "v1", "E01")
|
|
411
|
-
m.Width = 120
|
|
412
|
-
m.EpicPanelFocus = true
|
|
413
|
-
m.Epics = []string{"E01"}
|
|
414
|
-
m.Releases = []string{"v1", "v1.1"}
|
|
415
|
-
m.SelectedRelease = "v1.1"
|
|
416
|
-
|
|
417
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("r")})
|
|
418
|
-
updated := requireModel(t, got)
|
|
419
|
-
|
|
420
|
-
if updated.Overlay != OverlayRelease {
|
|
421
|
-
t.Errorf("Overlay = %q, want %q", updated.Overlay, OverlayRelease)
|
|
422
|
-
}
|
|
423
|
-
if updated.ReleaseCursor != 1 {
|
|
424
|
-
t.Errorf("ReleaseCursor = %d, want 1", updated.ReleaseCursor)
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
func TestView_epicDropdownOverlayRendered(t *testing.T) {
|
|
429
|
-
m := NewModel(nil, "v1", "E01")
|
|
430
|
-
m.Width = 80
|
|
431
|
-
m.Height = 24
|
|
432
|
-
m.Overlay = OverlayEpic
|
|
433
|
-
m.Epics = []string{"E01", "E02"}
|
|
434
|
-
got := m.View()
|
|
435
|
-
if !strings.Contains(got, "SELECT EPIC") {
|
|
436
|
-
t.Error("View() with OverlayEpic missing SELECT EPIC")
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
func TestView_epicDropdownKeepsBoardBehind(t *testing.T) {
|
|
441
|
-
m := NewModel(nil, "v1", "E01")
|
|
442
|
-
m.Width = 100
|
|
443
|
-
m.Height = 24
|
|
444
|
-
m.Overlay = OverlayEpic
|
|
445
|
-
m.Epics = []string{"E01", "E02"}
|
|
446
|
-
got := m.View()
|
|
447
|
-
if !strings.Contains(got, "S A V E P O I N T") {
|
|
448
|
-
t.Error("View() with OverlayEpic should keep board visible behind overlay")
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
func TestView_epicSidebarOnWide(t *testing.T) {
|
|
453
|
-
m := NewModel(nil, "v1", "E03")
|
|
454
|
-
m.Width = 120
|
|
455
|
-
m.Epics = []string{"E01", "E03"}
|
|
456
|
-
got := m.View()
|
|
457
|
-
if !strings.Contains(got, "EPICS") {
|
|
458
|
-
t.Error("View() at width>=120 missing EPICS header in sidebar")
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
func TestUpdate_epicDetailOverlayEscCloses(t *testing.T) {
|
|
463
|
-
m := NewModel(nil, "v1", "E01")
|
|
464
|
-
m.Width = 120
|
|
465
|
-
m.EpicPanelFocus = true
|
|
466
|
-
m.Epics = []string{"E01"}
|
|
467
|
-
m.Overlay = OverlayEpicDetail
|
|
468
|
-
m.EpicDetailContent = "# E01 Detail"
|
|
469
|
-
m.EpicDetailOffset = 3
|
|
470
|
-
|
|
471
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyEsc})
|
|
472
|
-
updated := requireModel(t, got)
|
|
473
|
-
|
|
474
|
-
if updated.Overlay != OverlayNone {
|
|
475
|
-
t.Errorf("Overlay = %q, want none after esc", updated.Overlay)
|
|
476
|
-
}
|
|
477
|
-
if !updated.EpicPanelFocus {
|
|
478
|
-
t.Error("EpicPanelFocus should remain true after closing detail overlay")
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
func TestUpdate_epicDetailOverlayScrollUpDown(t *testing.T) {
|
|
483
|
-
m := NewModel(nil, "v1", "E01")
|
|
484
|
-
m.Width = 120
|
|
485
|
-
m.EpicPanelFocus = true
|
|
486
|
-
m.Epics = []string{"E01"}
|
|
487
|
-
m.Overlay = OverlayEpicDetail
|
|
488
|
-
m.EpicDetailOffset = 2
|
|
489
|
-
|
|
490
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyDown})
|
|
491
|
-
updated := requireModel(t, got)
|
|
492
|
-
if updated.EpicDetailOffset != 3 {
|
|
493
|
-
t.Errorf("EpicDetailOffset after down = %d, want 3", updated.EpicDetailOffset)
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
got, _ = updated.Update(tea.KeyMsg{Type: tea.KeyUp})
|
|
497
|
-
updated = requireModel(t, got)
|
|
498
|
-
if updated.EpicDetailOffset != 2 {
|
|
499
|
-
t.Errorf("EpicDetailOffset after up = %d, want 2", updated.EpicDetailOffset)
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
got, _ = updated.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("k")})
|
|
503
|
-
updated = requireModel(t, got)
|
|
504
|
-
if updated.EpicDetailOffset != 1 {
|
|
505
|
-
t.Errorf("EpicDetailOffset after k = %d, want 1", updated.EpicDetailOffset)
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// Clamp at 0
|
|
509
|
-
updated.EpicDetailOffset = 0
|
|
510
|
-
got, _ = updated.Update(tea.KeyMsg{Type: tea.KeyUp})
|
|
511
|
-
updated = requireModel(t, got)
|
|
512
|
-
if updated.EpicDetailOffset != 0 {
|
|
513
|
-
t.Errorf("EpicDetailOffset should not go below 0, got %d", updated.EpicDetailOffset)
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
func TestUpdate_epicDetailOverlayPgUpDown(t *testing.T) {
|
|
518
|
-
m := NewModel(nil, "v1", "E01")
|
|
519
|
-
m.Width = 120
|
|
520
|
-
m.Height = 30
|
|
521
|
-
m.Epics = []string{"E01"}
|
|
522
|
-
m.Overlay = OverlayEpicDetail
|
|
523
|
-
m.EpicDetailOffset = 20
|
|
524
|
-
|
|
525
|
-
got, _ := m.Update(tea.KeyMsg{Type: tea.KeyPgUp})
|
|
526
|
-
updated := requireModel(t, got)
|
|
527
|
-
if updated.EpicDetailOffset >= 20 {
|
|
528
|
-
t.Errorf("EpicDetailOffset after pgup = %d, should decrease from 20", updated.EpicDetailOffset)
|
|
529
|
-
}
|
|
530
|
-
if updated.EpicDetailOffset < 0 {
|
|
531
|
-
t.Errorf("EpicDetailOffset = %d, should not go below 0", updated.EpicDetailOffset)
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
updated.EpicDetailOffset = 0
|
|
535
|
-
got, _ = updated.Update(tea.KeyMsg{Type: tea.KeyPgDown})
|
|
536
|
-
updated = requireModel(t, got)
|
|
537
|
-
if updated.EpicDetailOffset <= 0 {
|
|
538
|
-
t.Errorf("EpicDetailOffset after pgdown = %d, should increase from 0", updated.EpicDetailOffset)
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
func TestView_epicDetailOverlayRendered(t *testing.T) {
|
|
543
|
-
m := NewModel(nil, "v1", "E01")
|
|
544
|
-
m.Width = 120
|
|
545
|
-
m.Height = 30
|
|
546
|
-
m.Epics = []string{"E01"}
|
|
547
|
-
m.Overlay = OverlayEpicDetail
|
|
548
|
-
m.EpicDetailContent = "# My Epic\n\n## Purpose\nDoes things."
|
|
549
|
-
|
|
550
|
-
got := m.View()
|
|
551
|
-
if !strings.Contains(got, "EPIC DETAIL") {
|
|
552
|
-
t.Error("View() with OverlayEpicDetail missing EPIC DETAIL header")
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
func TestView_epicDetailOverlayNoContent(t *testing.T) {
|
|
557
|
-
m := NewModel(nil, "v1", "E01")
|
|
558
|
-
m.Width = 120
|
|
559
|
-
m.Height = 30
|
|
560
|
-
m.Epics = []string{"E01"}
|
|
561
|
-
m.Overlay = OverlayEpicDetail
|
|
562
|
-
m.EpicDetailContent = "(no detail available)"
|
|
563
|
-
|
|
564
|
-
got := m.View()
|
|
565
|
-
if !strings.Contains(got, "no detail available") {
|
|
566
|
-
t.Error("View() with missing epic detail should show 'no detail available'")
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
func TestRenderEpicDetail_stripsMarkdownHeadings(t *testing.T) {
|
|
571
|
-
content := "---\ntype: epic-design\n---\n# Epic E01\n\n## Purpose\nDoes things."
|
|
572
|
-
got := RenderEpicDetail("E01-test", content, 60, 40, 0, 0)
|
|
573
|
-
if !strings.Contains(got, "EPIC DETAIL") {
|
|
574
|
-
t.Error("RenderEpicDetail missing EPIC DETAIL header")
|
|
575
|
-
}
|
|
576
|
-
if strings.Contains(got, "# Epic E01") {
|
|
577
|
-
t.Error("RenderEpicDetail should strip raw markdown heading prefix")
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
func TestRenderEpicDetail_noDetailFallback(t *testing.T) {
|
|
582
|
-
got := RenderEpicDetail("E01-test", "(no detail available)", 60, 40, 0, 0)
|
|
583
|
-
if !strings.Contains(got, "no detail available") {
|
|
584
|
-
t.Error("RenderEpicDetail fallback message missing")
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
func TestRenderEpicDetail_tabIndicatorDetailActive(t *testing.T) {
|
|
589
|
-
got := RenderEpicDetail("E01-test", "content", 60, 40, 0, 0)
|
|
590
|
-
if !strings.Contains(got, "DETAIL [1]") {
|
|
591
|
-
t.Error("RenderEpicDetail tab=0: missing DETAIL [1] indicator")
|
|
592
|
-
}
|
|
593
|
-
if !strings.Contains(got, "AUDIT [2]") {
|
|
594
|
-
t.Error("RenderEpicDetail tab=0: missing AUDIT [2] indicator")
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
func TestRenderEpicDetail_tabIndicatorAuditActive(t *testing.T) {
|
|
599
|
-
got := RenderEpicDetail("E01-test", "content", 60, 40, 0, 1)
|
|
600
|
-
if !strings.Contains(got, "DETAIL [1]") {
|
|
601
|
-
t.Error("RenderEpicDetail tab=1: missing DETAIL [1] indicator")
|
|
602
|
-
}
|
|
603
|
-
if !strings.Contains(got, "AUDIT [2]") {
|
|
604
|
-
t.Error("RenderEpicDetail tab=1: missing AUDIT [2] indicator")
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
func TestRenderEpicAuditTab_header(t *testing.T) {
|
|
609
|
-
got := RenderEpicAuditTab("E06-test", "# Audit\n\n## Main Findings\nAll good.", 60, 40, 0, 1)
|
|
610
|
-
if !strings.Contains(got, "EPIC AUDIT") {
|
|
611
|
-
t.Error("RenderEpicAuditTab missing EPIC AUDIT header")
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
func TestRenderEpicAuditTab_noContent(t *testing.T) {
|
|
616
|
-
got := RenderEpicAuditTab("E06-test", "(no audit available)", 60, 40, 0, 1)
|
|
617
|
-
if !strings.Contains(got, "no audit available") {
|
|
618
|
-
t.Error("RenderEpicAuditTab fallback message missing")
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
func TestRenderEpicAuditTab_emptyContent(t *testing.T) {
|
|
623
|
-
got := RenderEpicAuditTab("E06-test", "", 60, 40, 0, 1)
|
|
624
|
-
if !strings.Contains(got, "no audit available") {
|
|
625
|
-
t.Error("RenderEpicAuditTab empty content should show fallback")
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
func TestRenderEpicAuditTab_stripsFrontmatter(t *testing.T) {
|
|
630
|
-
content := "---\ntype: audit\n---\n# E06 Audit\n\n## Main Findings\nLooks good."
|
|
631
|
-
got := RenderEpicAuditTab("E06-test", content, 60, 40, 0, 1)
|
|
632
|
-
if strings.Contains(got, "type: audit") {
|
|
633
|
-
t.Error("RenderEpicAuditTab should strip frontmatter")
|
|
634
|
-
}
|
|
635
|
-
if !strings.Contains(got, "EPIC AUDIT") {
|
|
636
|
-
t.Error("RenderEpicAuditTab missing header after frontmatter strip")
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
func TestRenderEpicAuditTab_checkboxDonePresent(t *testing.T) {
|
|
641
|
-
content := "## Code Style Review\n- [x] One job per file\n- [ ] One-sentence functions"
|
|
642
|
-
got := RenderEpicAuditTab("E06-test", content, 60, 40, 0, 1)
|
|
643
|
-
if !strings.Contains(got, "One job per file") {
|
|
644
|
-
t.Error("RenderEpicAuditTab missing done checkbox text")
|
|
645
|
-
}
|
|
646
|
-
if !strings.Contains(got, "One-sentence functions") {
|
|
647
|
-
t.Error("RenderEpicAuditTab missing undone checkbox text")
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
func TestRenderEpicAuditTab_scrollFooter(t *testing.T) {
|
|
652
|
-
got := RenderEpicAuditTab("E06-test", "# Audit", 60, 40, 0, 1)
|
|
653
|
-
if !strings.Contains(got, "esc:close") {
|
|
654
|
-
t.Error("RenderEpicAuditTab missing esc:close footer")
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
func TestRenderEpicAuditTab_tabIndicator(t *testing.T) {
|
|
659
|
-
got := RenderEpicAuditTab("E06-test", "# Audit", 60, 40, 0, 1)
|
|
660
|
-
if !strings.Contains(got, "DETAIL [1]") {
|
|
661
|
-
t.Error("RenderEpicAuditTab missing DETAIL [1] indicator")
|
|
662
|
-
}
|
|
663
|
-
if !strings.Contains(got, "AUDIT [2]") {
|
|
664
|
-
t.Error("RenderEpicAuditTab missing AUDIT [2] indicator")
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
func TestRenderEpicAuditTab_mainFindingsVisible(t *testing.T) {
|
|
669
|
-
content := "## Main Findings\nAudit summary is visible.\n\n## Proposed Changes\n### Target File\nAGENTS.md\n"
|
|
670
|
-
got := RenderEpicAuditTab("E06-test", content, 80, 50, 0, 1)
|
|
671
|
-
if !strings.Contains(got, "Audit summary is visible") {
|
|
672
|
-
t.Error("RenderEpicAuditTab should render Main Findings body")
|
|
673
|
-
}
|
|
674
|
-
if strings.Contains(got, "Target File") || strings.Contains(got, "AGENTS.md") {
|
|
675
|
-
t.Error("RenderEpicAuditTab should not render Proposed Changes admin blocks")
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
func TestRenderEpicAuditTab_qualityReviewHidden(t *testing.T) {
|
|
680
|
-
content := "## Quality Review\nOld quality section.\n\n## Code Style Review\n- [ ] One job per file\n"
|
|
681
|
-
got := RenderEpicAuditTab("E06-test", content, 80, 50, 0, 1)
|
|
682
|
-
if strings.Contains(got, "Old quality section") {
|
|
683
|
-
t.Error("RenderEpicAuditTab should not render superseded Quality Review section")
|
|
684
|
-
}
|
|
685
|
-
if !strings.Contains(got, "One job per file") {
|
|
686
|
-
t.Error("RenderEpicAuditTab should render Code Style Review")
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
func TestRenderEpicAuditTab_hiddenHeadingsRequireExactMatch(t *testing.T) {
|
|
691
|
-
content := "## Proposed Changes Appendix\nNear-match section is visible.\n\n## Proposed Changes\nHidden admin section.\n"
|
|
692
|
-
got := RenderEpicAuditTab("E06-test", content, 80, 50, 0, 1)
|
|
693
|
-
if !strings.Contains(got, "Near-match section is visible") {
|
|
694
|
-
t.Error("RenderEpicAuditTab should render headings that only partially match hidden headings")
|
|
695
|
-
}
|
|
696
|
-
if strings.Contains(got, "Hidden admin section") {
|
|
697
|
-
t.Error("RenderEpicAuditTab should hide exact Proposed Changes section")
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
func TestRenderEpicAuditTab_allCodeStyleRules(t *testing.T) {
|
|
702
|
-
rules := []string{
|
|
703
|
-
"One job per file",
|
|
704
|
-
"One-sentence functions",
|
|
705
|
-
"Test branches",
|
|
706
|
-
"Types are documentation",
|
|
707
|
-
"Build, don't speculate",
|
|
708
|
-
"Errors at boundaries",
|
|
709
|
-
"One source of truth",
|
|
710
|
-
"Comments explain WHY",
|
|
711
|
-
"Content in data files",
|
|
712
|
-
"Small diffs",
|
|
713
|
-
}
|
|
714
|
-
content := "## Code Style Review\n"
|
|
715
|
-
for _, r := range rules {
|
|
716
|
-
content += "- [ ] " + r + "\n"
|
|
717
|
-
}
|
|
718
|
-
got := RenderEpicAuditTab("E06-test", content, 80, 50, 0, 1)
|
|
719
|
-
for _, r := range rules {
|
|
720
|
-
if !strings.Contains(got, r) {
|
|
721
|
-
t.Errorf("RenderEpicAuditTab missing code style rule %q", r)
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
// TestView_epicAuditTabRendered verifies View() uses RenderEpicAuditTab when EpicDetailTab=1.
|
|
727
|
-
func TestView_epicAuditTabRendered(t *testing.T) {
|
|
728
|
-
m := NewModel(nil, "v1.1", "E06-audit-command")
|
|
729
|
-
m.Width = 120
|
|
730
|
-
m.Height = 30
|
|
731
|
-
m.Epics = []string{"E06-audit-command"}
|
|
732
|
-
m.Overlay = OverlayEpicDetail
|
|
733
|
-
m.EpicDetailTab = 1
|
|
734
|
-
m.EpicAuditContent = "# Audit Findings: E06\n\n## Main Findings\nAll good.\n\n## Code Style Review\n- [x] One job per file\n"
|
|
735
|
-
|
|
736
|
-
got := m.View()
|
|
737
|
-
if !strings.Contains(got, "EPIC AUDIT") {
|
|
738
|
-
t.Error("View() with EpicDetailTab=1 missing EPIC AUDIT header")
|
|
739
|
-
}
|
|
740
|
-
if strings.Contains(got, "EPIC DETAIL") {
|
|
741
|
-
t.Error("View() with EpicDetailTab=1 should not render EPIC DETAIL header")
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
// TestAuditWorkflow_fullEndToEnd exercises the full audit workflow:
|
|
746
|
-
// create E##-Audit.md on disk, open overlay, press 2, verify content loads and renders.
|
|
747
|
-
func TestAuditWorkflow_fullEndToEnd(t *testing.T) {
|
|
748
|
-
root := t.TempDir()
|
|
749
|
-
epicSlug := "E06-audit-command"
|
|
750
|
-
epicDir := filepath.Join(root, "releases", "v1.1", "epics", epicSlug)
|
|
751
|
-
if err := os.MkdirAll(epicDir, 0755); err != nil {
|
|
752
|
-
t.Fatal(err)
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
auditContent := `---
|
|
756
|
-
type: audit-findings
|
|
757
|
-
audited: 2026-05-03
|
|
758
|
-
---
|
|
759
|
-
# Audit Findings: E06 Agent Audit + Audit Tab
|
|
760
|
-
|
|
761
|
-
## Main Findings
|
|
762
|
-
All acceptance criteria met.
|
|
763
|
-
|
|
764
|
-
## Code Style Review
|
|
765
|
-
- [x] One job per file
|
|
766
|
-
- [x] One-sentence functions
|
|
767
|
-
- [x] Test branches
|
|
768
|
-
- [x] Types are documentation
|
|
769
|
-
- [x] Build, don't speculate
|
|
770
|
-
- [x] Errors at boundaries
|
|
771
|
-
- [x] One source of truth
|
|
772
|
-
- [x] Comments explain WHY
|
|
773
|
-
- [x] Content in data files
|
|
774
|
-
- [x] Small diffs
|
|
775
|
-
`
|
|
776
|
-
if err := os.WriteFile(filepath.Join(epicDir, "E06-Audit.md"), []byte(auditContent), 0644); err != nil {
|
|
777
|
-
t.Fatal(err)
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
tasks := []data.Task{
|
|
781
|
-
{ID: "E06-audit-command/T009-integration", Release: "v1.1", Epic: epicSlug, Column: data.ColumnPlanned},
|
|
782
|
-
}
|
|
783
|
-
m := NewModel(tasks, "v1.1", epicSlug)
|
|
784
|
-
m.Root = root
|
|
785
|
-
m.Epics = []string{epicSlug}
|
|
786
|
-
m.EpicPanelCursor = 0
|
|
787
|
-
m.Width = 120
|
|
788
|
-
m.Height = 40
|
|
789
|
-
|
|
790
|
-
// Open detail overlay (tab=0)
|
|
791
|
-
m.openEpicDetailOverlay()
|
|
792
|
-
if m.Overlay != OverlayEpicDetail {
|
|
793
|
-
t.Fatal("overlay not opened")
|
|
794
|
-
}
|
|
795
|
-
if m.EpicDetailTab != 0 {
|
|
796
|
-
t.Errorf("EpicDetailTab = %d, want 0 on open", m.EpicDetailTab)
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
// Press 2 → switch to audit tab, load content
|
|
800
|
-
got, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("2")})
|
|
801
|
-
updated := requireModel(t, got)
|
|
802
|
-
|
|
803
|
-
if updated.EpicDetailTab != 1 {
|
|
804
|
-
t.Errorf("EpicDetailTab = %d, want 1 after pressing 2", updated.EpicDetailTab)
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
msg := cmd()
|
|
808
|
-
got2, _ := updated.Update(msg)
|
|
809
|
-
updated2 := requireModel(t, got2)
|
|
810
|
-
if updated2.EpicAuditContent == "" || updated2.EpicAuditContent == "(no audit available)" {
|
|
811
|
-
t.Errorf("EpicAuditContent not loaded: %q", updated2.EpicAuditContent)
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
// Verify View() renders audit content
|
|
815
|
-
view := updated2.View()
|
|
816
|
-
if !strings.Contains(view, "EPIC AUDIT") {
|
|
817
|
-
t.Error("View() after tab switch missing EPIC AUDIT")
|
|
818
|
-
}
|
|
819
|
-
if !strings.Contains(view, "One job per file") {
|
|
820
|
-
t.Error("View() after tab switch missing code style rule")
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
// Press 1 → switch back to detail tab
|
|
824
|
-
got, _ = updated2.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("1")})
|
|
825
|
-
updated = requireModel(t, got)
|
|
826
|
-
if updated.EpicDetailTab != 0 {
|
|
827
|
-
t.Errorf("EpicDetailTab = %d, want 0 after pressing 1", updated.EpicDetailTab)
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
// Press esc → overlay closes
|
|
831
|
-
got, _ = updated.Update(tea.KeyMsg{Type: tea.KeyEsc})
|
|
832
|
-
updated = requireModel(t, got)
|
|
833
|
-
if updated.Overlay != OverlayNone {
|
|
834
|
-
t.Errorf("Overlay = %q, want none after esc", updated.Overlay)
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
func TestRenderEpicAuditTab_v11AuditFiles(t *testing.T) {
|
|
839
|
-
files := []struct {
|
|
840
|
-
path string
|
|
841
|
-
want string
|
|
842
|
-
}{
|
|
843
|
-
{filepath.Join("..", "..", ".savepoint", "releases", "v1.1", "epics", "E02-cross-platform-compatibility", "E02-Audit.md"), "cross-platform build work"},
|
|
844
|
-
{filepath.Join("..", "..", ".savepoint", "releases", "v1.1", "epics", "E03-ui-visual-refinement", "E03-Audit.md"), "visual refinement work"},
|
|
845
|
-
{filepath.Join("..", "..", ".savepoint", "releases", "v1.1", "epics", "E04-epic-navigation", "E04-Audit.md"), "wide-screen epic navigation"},
|
|
846
|
-
{filepath.Join("..", "..", ".savepoint", "releases", "v1.1", "epics", "E05-tasking-permissions", "E05-Audit.md"), "tasking-permissions shift"},
|
|
847
|
-
{filepath.Join("..", "..", ".savepoint", "releases", "v1.1", "epics", "E06-audit-command", "E06-Audit.md"), "agent-led"},
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
for _, tt := range files {
|
|
851
|
-
content, err := os.ReadFile(tt.path)
|
|
852
|
-
if err != nil {
|
|
853
|
-
t.Fatalf("read %s: %v", tt.path, err)
|
|
854
|
-
}
|
|
855
|
-
if !strings.Contains(string(content), tt.want) {
|
|
856
|
-
t.Fatalf("fixture %s missing %q", tt.path, tt.want)
|
|
857
|
-
}
|
|
858
|
-
got := RenderEpicAuditTab(filepath.Base(filepath.Dir(tt.path)), string(content), 80, 40, 0, 1)
|
|
859
|
-
if !strings.Contains(got, tt.want) {
|
|
860
|
-
t.Errorf("RenderEpicAuditTab(%s) missing %q", tt.path, tt.want)
|
|
861
|
-
}
|
|
862
|
-
if strings.Contains(got, "Target File") {
|
|
863
|
-
t.Errorf("RenderEpicAuditTab(%s) should not render Proposed Changes", tt.path)
|
|
864
|
-
}
|
|
865
|
-
if strings.Contains(got, "Boundaries") || strings.Contains(got, "Implemented as") || strings.Contains(got, "Implemented As") {
|
|
866
|
-
t.Errorf("RenderEpicAuditTab(%s) should only render visible audit sections", tt.path)
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
}
|