savepoint 1.0.0
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/.claude/settings.local.json +20 -0
- package/.prettierignore +4 -0
- package/.savepoint/Design.md +196 -0
- package/.savepoint/PRD.md +58 -0
- package/.savepoint/audit/E01-go-setup/proposals.md +166 -0
- package/.savepoint/audit/E01-go-setup/snapshot.md +71 -0
- package/.savepoint/audit/E01-scaffolding/proposals/AGENTS.md +66 -0
- package/.savepoint/audit/E01-scaffolding/proposals/Design.md +210 -0
- package/.savepoint/audit/E01-scaffolding/proposals/epic-Design.md +117 -0
- package/.savepoint/audit/E01-scaffolding/proposals/quality-review.md +101 -0
- package/.savepoint/audit/E01-scaffolding/snapshot.md +54 -0
- package/.savepoint/audit/E02-data-model/snapshot.md +128 -0
- package/.savepoint/audit/E02-data-readers/proposals.md +123 -0
- package/.savepoint/audit/E02-data-readers/snapshot.md +54 -0
- package/.savepoint/audit/E03-board-tui-core/proposals.md +146 -0
- package/.savepoint/audit/E03-board-tui-core/snapshot.md +57 -0
- package/.savepoint/audit/E03-cli-foundation/snapshot.md +106 -0
- package/.savepoint/audit/E04-board-components/proposals.md +118 -0
- package/.savepoint/audit/E04-board-components/snapshot.md +77 -0
- package/.savepoint/audit/E04-templates-and-prompts/snapshot.md +115 -0
- package/.savepoint/audit/E05-init-command/snapshot.md +125 -0
- package/.savepoint/audit/E05-phase-transitions/proposals.md +83 -0
- package/.savepoint/audit/E05-phase-transitions/snapshot.md +36 -0
- package/.savepoint/audit/E06-tui-board/snapshot.md +64 -0
- package/.savepoint/audit/E07-audit-pipeline/snapshot.md +165 -0
- package/.savepoint/audit/E08-board-workflow-cleanup/snapshot.md +65 -0
- package/.savepoint/config.yml +27 -0
- package/.savepoint/releases/v1/PRD.md +66 -0
- package/.savepoint/releases/v1/epics/E01-go-setup/Design.md +39 -0
- package/.savepoint/releases/v1/epics/E01-go-setup/tasks/T001-init-module.md +42 -0
- package/.savepoint/releases/v1/epics/E01-go-setup/tasks/T002-entrypoint.md +23 -0
- package/.savepoint/releases/v1/epics/E01-go-setup/tasks/T003-directory-structure.md +24 -0
- package/.savepoint/releases/v1/epics/E01-go-setup/tasks/T004-makefile.md +23 -0
- package/.savepoint/releases/v1/epics/E02-data-readers/Design.md +61 -0
- package/.savepoint/releases/v1/epics/E02-data-readers/tasks/T001-task-struct.md +29 -0
- package/.savepoint/releases/v1/epics/E02-data-readers/tasks/T002-frontmatter-parser.md +30 -0
- package/.savepoint/releases/v1/epics/E02-data-readers/tasks/T003-router-reader.md +29 -0
- package/.savepoint/releases/v1/epics/E02-data-readers/tasks/T004-config-reader.md +29 -0
- package/.savepoint/releases/v1/epics/E02-data-readers/tasks/T005-discovery.md +30 -0
- package/.savepoint/releases/v1/epics/E03-board-tui-core/Design.md +38 -0
- package/.savepoint/releases/v1/epics/E03-board-tui-core/tasks/T001-model.md +29 -0
- package/.savepoint/releases/v1/epics/E03-board-tui-core/tasks/T002-update-loop.md +30 -0
- package/.savepoint/releases/v1/epics/E03-board-tui-core/tasks/T003-view.md +34 -0
- package/.savepoint/releases/v1/epics/E03-board-tui-core/tasks/T004-styles.md +29 -0
- package/.savepoint/releases/v1/epics/E03-board-tui-core/tasks/T005-layout.md +42 -0
- package/.savepoint/releases/v1/epics/E04-board-components/Design.md +44 -0
- package/.savepoint/releases/v1/epics/E04-board-components/tasks/T001-column.md +34 -0
- package/.savepoint/releases/v1/epics/E04-board-components/tasks/T002-card.md +33 -0
- package/.savepoint/releases/v1/epics/E04-board-components/tasks/T003-epic-panel.md +49 -0
- package/.savepoint/releases/v1/epics/E04-board-components/tasks/T004-detail-overlay.md +40 -0
- package/.savepoint/releases/v1/epics/E04-board-components/tasks/T005-release-dropdown.md +33 -0
- package/.savepoint/releases/v1/epics/E04-board-components/tasks/T006-help-overlay.md +34 -0
- package/.savepoint/releases/v1/epics/E05-phase-transitions/Design.md +38 -0
- package/.savepoint/releases/v1/epics/E05-phase-transitions/tasks/T001-phase-stepping.md +29 -0
- package/.savepoint/releases/v1/epics/E05-phase-transitions/tasks/T002-gates.md +31 -0
- package/.savepoint/releases/v1/epics/E05-phase-transitions/tasks/T003-write-task.md +31 -0
- package/.savepoint/releases/v1/epics/E05-phase-transitions/tasks/T004-write-router.md +31 -0
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/Design.md +42 -0
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T001-color-system.md +39 -0
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T002-header-and-dividers.md +52 -0
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T003-footer-status-bar.md +52 -0
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T004-component-refinement.md +53 -0
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T005-restore-nav-hints.md +39 -0
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T007-detail-card-fixes.md +36 -0
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T008-checkbox-states.md +38 -0
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T009-router-priority-marker.md +41 -0
- package/.savepoint/releases/v1/epics/E06-atari-noir-layout/tasks/T010-auto-refresh-watcher.md +61 -0
- package/.savepoint/releases/v1/epics/_archived/E01-archive-and-reset/Design.md +39 -0
- package/.savepoint/releases/v1/epics/_archived/E01-archive-and-reset/tasks/T001-archive-epics.md +20 -0
- package/.savepoint/releases/v1/epics/_archived/E01-archive-and-reset/tasks/T002-rewrite-prd.md +22 -0
- package/.savepoint/releases/v1/epics/_archived/E01-archive-and-reset/tasks/T003-create-epic-stubs.md +24 -0
- package/.savepoint/releases/v1/epics/_archived/E01-archive-and-reset/tasks/T004-update-router.md +22 -0
- package/.savepoint/releases/v1/epics/_archived/E01-scaffolding/Design.md +118 -0
- package/.savepoint/releases/v1/epics/_archived/E01-scaffolding/handoff.md +9 -0
- package/.savepoint/releases/v1/epics/_archived/E01-scaffolding/tasks/T001-package-baseline.md +45 -0
- package/.savepoint/releases/v1/epics/_archived/E01-scaffolding/tasks/T002-typescript-build.md +48 -0
- package/.savepoint/releases/v1/epics/_archived/E01-scaffolding/tasks/T003-vitest-smoke.md +43 -0
- package/.savepoint/releases/v1/epics/_archived/E01-scaffolding/tasks/T004-lint-format-gates.md +45 -0
- package/.savepoint/releases/v1/epics/_archived/E01-scaffolding/tasks/T005-scaffold-verification.md +40 -0
- package/.savepoint/releases/v1/epics/_archived/E02-data-model/Design.md +142 -0
- package/.savepoint/releases/v1/epics/_archived/E02-data-model/tasks/T001-domain-ids-status.md +27 -0
- package/.savepoint/releases/v1/epics/_archived/E02-data-model/tasks/T002-markdown-frontmatter-boundary.md +28 -0
- package/.savepoint/releases/v1/epics/_archived/E02-data-model/tasks/T003-task-documents.md +29 -0
- package/.savepoint/releases/v1/epics/_archived/E02-data-model/tasks/T004-release-epic-router-config-readers.md +30 -0
- package/.savepoint/releases/v1/epics/_archived/E02-data-model/tasks/T005-dependency-validation.md +29 -0
- package/.savepoint/releases/v1/epics/_archived/E02-data-model/tasks/T006-epic-task-set-reader.md +29 -0
- package/.savepoint/releases/v1/epics/_archived/E02-data-model/tasks/T007-quality-gates.md +31 -0
- package/.savepoint/releases/v1/epics/_archived/E02-domain-phase-model/Design.md +40 -0
- package/.savepoint/releases/v1/epics/_archived/E02-domain-phase-model/tasks/T001-phase-types.md +27 -0
- package/.savepoint/releases/v1/epics/_archived/E02-domain-phase-model/tasks/T002-phase-frontmatter.md +25 -0
- package/.savepoint/releases/v1/epics/_archived/E02-domain-phase-model/tasks/T003-simplify-config.md +26 -0
- package/.savepoint/releases/v1/epics/_archived/E02-domain-phase-model/tasks/T004-simplify-router-domain.md +24 -0
- package/.savepoint/releases/v1/epics/_archived/E03-cli-foundation/Design.md +122 -0
- package/.savepoint/releases/v1/epics/_archived/E03-cli-foundation/tasks/T001-argument-parser-contract.md +28 -0
- package/.savepoint/releases/v1/epics/_archived/E03-cli-foundation/tasks/T002-help-text-generation.md +28 -0
- package/.savepoint/releases/v1/epics/_archived/E03-cli-foundation/tasks/T003-terminal-environment-detection.md +27 -0
- package/.savepoint/releases/v1/epics/_archived/E03-cli-foundation/tasks/T004-command-stub-modules.md +29 -0
- package/.savepoint/releases/v1/epics/_archived/E03-cli-foundation/tasks/T005-cli-runner-dispatch.md +34 -0
- package/.savepoint/releases/v1/epics/_archived/E03-cli-foundation/tasks/T006-entrypoint-quality-gates.md +32 -0
- package/.savepoint/releases/v1/epics/_archived/E03-cli-simplify/Design.md +43 -0
- package/.savepoint/releases/v1/epics/_archived/E03-cli-simplify/tasks/T001-strip-args.md +26 -0
- package/.savepoint/releases/v1/epics/_archived/E03-cli-simplify/tasks/T002-strip-help.md +23 -0
- package/.savepoint/releases/v1/epics/_archived/E03-cli-simplify/tasks/T003-strip-run.md +23 -0
- package/.savepoint/releases/v1/epics/_archived/E03-cli-simplify/tasks/T004-delete-commands.md +24 -0
- package/.savepoint/releases/v1/epics/_archived/E03-cli-simplify/tasks/T005-update-cli-tests.md +22 -0
- package/.savepoint/releases/v1/epics/_archived/E04-board-phase-integration/Design.md +48 -0
- package/.savepoint/releases/v1/epics/_archived/E04-board-phase-integration/tasks/T001-board-data-phases.md +26 -0
- package/.savepoint/releases/v1/epics/_archived/E04-board-phase-integration/tasks/T002-phase-rendering.md +28 -0
- package/.savepoint/releases/v1/epics/_archived/E04-board-phase-integration/tasks/T003-detail-pane-phases.md +27 -0
- package/.savepoint/releases/v1/epics/_archived/E04-board-phase-integration/tasks/T004-phase-transitions.md +42 -0
- package/.savepoint/releases/v1/epics/_archived/E04-board-phase-integration/tasks/T005-phase-gates.md +24 -0
- package/.savepoint/releases/v1/epics/_archived/E04-board-phase-integration/tasks/T006-phase-write-back.md +24 -0
- package/.savepoint/releases/v1/epics/_archived/E04-board-phase-integration/tasks/T007-remove-audit-flow.md +27 -0
- package/.savepoint/releases/v1/epics/_archived/E04-board-phase-integration/tasks/T008-board-tests.md +25 -0
- package/.savepoint/releases/v1/epics/_archived/E04-templates-and-prompts/Design.md +85 -0
- package/.savepoint/releases/v1/epics/_archived/E04-templates-and-prompts/tasks/T001-project-template-assets.md +17 -0
- package/.savepoint/releases/v1/epics/_archived/E04-templates-and-prompts/tasks/T002-release-and-prompt-assets.md +20 -0
- package/.savepoint/releases/v1/epics/_archived/E04-templates-and-prompts/tasks/T003-template-registry-renderer.md +22 -0
- package/.savepoint/releases/v1/epics/_archived/E04-templates-and-prompts/tasks/T004-template-integrity-tests.md +17 -0
- package/.savepoint/releases/v1/epics/_archived/E04-templates-and-prompts/tasks/T005-template-closeout-quality-gates.md +16 -0
- package/.savepoint/releases/v1/epics/_archived/E05-init-command/Design.md +88 -0
- package/.savepoint/releases/v1/epics/_archived/E05-init-command/tasks/T001-init-cli-contract.md +22 -0
- package/.savepoint/releases/v1/epics/_archived/E05-init-command/tasks/T002-target-validation.md +23 -0
- package/.savepoint/releases/v1/epics/_archived/E05-init-command/tasks/T003-scaffold-writer.md +24 -0
- package/.savepoint/releases/v1/epics/_archived/E05-init-command/tasks/T004-magic-prompt-and-clipboard.md +23 -0
- package/.savepoint/releases/v1/epics/_archived/E05-init-command/tasks/T005-dev-deps-install-option.md +24 -0
- package/.savepoint/releases/v1/epics/_archived/E05-init-command/tasks/T006-init-command-integration.md +28 -0
- package/.savepoint/releases/v1/epics/_archived/E05-project-cleanup/Design.md +53 -0
- package/.savepoint/releases/v1/epics/_archived/E05-project-cleanup/tasks/T001-delete-dead-src.md +23 -0
- package/.savepoint/releases/v1/epics/_archived/E05-project-cleanup/tasks/T002-delete-dead-tests.md +26 -0
- package/.savepoint/releases/v1/epics/_archived/E05-project-cleanup/tasks/T003-delete-assets.md +25 -0
- package/.savepoint/releases/v1/epics/_archived/E05-project-cleanup/tasks/T004-clean-savepoint.md +28 -0
- package/.savepoint/releases/v1/epics/_archived/E05-project-cleanup/tasks/T005-rewrite-agents-md.md +28 -0
- package/.savepoint/releases/v1/epics/_archived/E05-project-cleanup/tasks/T006-clean-package-json.md +23 -0
- package/.savepoint/releases/v1/epics/_archived/E05-project-cleanup/tasks/T007-verify.md +25 -0
- package/.savepoint/releases/v1/epics/_archived/E06-tui-board/Design.md +104 -0
- package/.savepoint/releases/v1/epics/_archived/E06-tui-board/tasks/T001-board-command-data.md +23 -0
- package/.savepoint/releases/v1/epics/_archived/E06-tui-board/tasks/T002-board-view-state.md +24 -0
- package/.savepoint/releases/v1/epics/_archived/E06-tui-board/tasks/T003-transition-gates-and-writes.md +25 -0
- package/.savepoint/releases/v1/epics/_archived/E06-tui-board/tasks/T004-terminal-theme.md +23 -0
- package/.savepoint/releases/v1/epics/_archived/E06-tui-board/tasks/T005-ink-board-ui.md +26 -0
- package/.savepoint/releases/v1/epics/_archived/E06-tui-board/tasks/T006-board-integration-audit-entry.md +24 -0
- package/.savepoint/releases/v1/epics/_archived/E07-audit-pipeline/Design.md +88 -0
- package/.savepoint/releases/v1/epics/_archived/E07-audit-pipeline/tasks/T001-audit-cli-contract.md +23 -0
- package/.savepoint/releases/v1/epics/_archived/E07-audit-pipeline/tasks/T002-quality-gate-runner.md +23 -0
- package/.savepoint/releases/v1/epics/_archived/E07-audit-pipeline/tasks/T003-snapshot-and-prompt.md +23 -0
- package/.savepoint/releases/v1/epics/_archived/E07-audit-pipeline/tasks/T004-audit-orchestration-router.md +27 -0
- package/.savepoint/releases/v1/epics/_archived/E07-audit-pipeline/tasks/T005-proposal-validation-apply.md +25 -0
- package/.savepoint/releases/v1/epics/_archived/E07-audit-pipeline/tasks/T006-audit-review-state.md +24 -0
- package/.savepoint/releases/v1/epics/_archived/E07-audit-pipeline/tasks/T007-audit-review-ui.md +26 -0
- package/.savepoint/releases/v1/epics/_archived/E07-audit-pipeline/tasks/T008-audit-pipeline-integration.md +24 -0
- package/.savepoint/releases/v1/epics/_archived/E08-board-workflow-cleanup/Design.md +103 -0
- package/.savepoint/releases/v1/epics/_archived/E08-board-workflow-cleanup/tasks/T001-acceptance-criteria-model.md +30 -0
- package/.savepoint/releases/v1/epics/_archived/E08-board-workflow-cleanup/tasks/T002-release-task-set-reader.md +33 -0
- package/.savepoint/releases/v1/epics/_archived/E08-board-workflow-cleanup/tasks/T003-board-data-and-plain-output.md +34 -0
- package/.savepoint/releases/v1/epics/_archived/E08-board-workflow-cleanup/tasks/T004-board-selection-state.md +33 -0
- package/.savepoint/releases/v1/epics/_archived/E08-board-workflow-cleanup/tasks/T005-ink-board-layout-cleanup.md +37 -0
- package/.savepoint/releases/v1/epics/_archived/E08-board-workflow-cleanup/tasks/T006-task-detail-popup.md +36 -0
- package/.savepoint/releases/v1/epics/_archived/E08-board-workflow-cleanup/tasks/T007-templates-acceptance-criteria.md +34 -0
- package/.savepoint/releases/v1/epics/_archived/E08-board-workflow-cleanup/tasks/T008-board-workflow-integration.md +41 -0
- package/.savepoint/releases/v1/epics/_archived/E09-doctor-command/Design.md +70 -0
- package/.savepoint/releases/v1/epics/_archived/E10-docs-and-packaging/Design.md +68 -0
- package/.savepoint/releases/v1/epics/_archived/E11-release-validation/Design.md +68 -0
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/Design.md +26 -0
- package/.savepoint/releases/v1.1/epics/E01-tui-optimisation/tasks/T001-border-resize-fix.md +35 -0
- package/.savepoint/router.md +136 -0
- package/.savepoint/visual-identity.md +124 -0
- package/AGENTS.md +141 -0
- package/CLAUDE.md +1 -0
- package/GEMINI.md +1 -0
- package/LICENSE +21 -0
- package/Makefile +13 -0
- package/README.md +78 -0
- package/agent-skills/ink-tui-design/SKILL.md +309 -0
- package/agent-skills/ink-tui-design/references/component-patterns.md +371 -0
- package/agent-skills/ink-tui-design/references/hooks-guide.md +436 -0
- package/agent-skills/ink-tui-design/references/ink-gotchas.md +330 -0
- package/agent-skills/ink-tui-design/references/testing-patterns.md +384 -0
- package/agent-skills/savepoint-audit/SKILL.md +35 -0
- package/agent-skills/savepoint-build-task/SKILL.md +39 -0
- package/agent-skills/savepoint-create-plan/SKILL.md +28 -0
- package/agent-skills/savepoint-create-task/SKILL.md +31 -0
- package/agent-skills/savepoint-draft-prd/SKILL.md +32 -0
- package/agent-skills/savepoint-system-design/SKILL.md +33 -0
- package/agent-skills/superpowers/brainstorming/SKILL.md +165 -0
- package/agent-skills/superpowers/brainstorming/visual-companion.md +304 -0
- package/agent-skills/superpowers/dispatching-parallel-agents/SKILL.md +193 -0
- package/agent-skills/superpowers/executing-plans/SKILL.md +77 -0
- package/agent-skills/superpowers/finishing-a-development-branch/SKILL.md +213 -0
- package/agent-skills/superpowers/receiving-code-review/SKILL.md +226 -0
- package/agent-skills/superpowers/requesting-code-review/SKILL.md +115 -0
- package/agent-skills/superpowers/requesting-code-review/code-reviewer.md +160 -0
- package/agent-skills/superpowers/subagent-driven-development/SKILL.md +292 -0
- package/agent-skills/superpowers/subagent-driven-development/code-quality-reviewer-prompt.md +27 -0
- package/agent-skills/superpowers/subagent-driven-development/implementer-prompt.md +113 -0
- package/agent-skills/superpowers/subagent-driven-development/spec-reviewer-prompt.md +61 -0
- package/agent-skills/superpowers/systematic-debugging/SKILL.md +305 -0
- package/agent-skills/superpowers/systematic-debugging/condition-based-waiting.md +122 -0
- package/agent-skills/superpowers/systematic-debugging/defense-in-depth.md +130 -0
- package/agent-skills/superpowers/systematic-debugging/root-cause-tracing.md +183 -0
- package/agent-skills/superpowers/test-driven-development/SKILL.md +389 -0
- package/agent-skills/superpowers/test-driven-development/testing-anti-patterns.md +317 -0
- package/agent-skills/superpowers/verification-before-completion/SKILL.md +147 -0
- package/agent-skills/superpowers/writing-plans/SKILL.md +159 -0
- package/agent-skills/superpowers/writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/assets/banner.png +0 -0
- package/assets/logo.png +0 -0
- package/assets/strawman.png +0 -0
- package/go.mod +33 -0
- package/go.sum +73 -0
- package/ink-cli-ui-design.zip +0 -0
- package/internal/board/board.go +121 -0
- package/internal/board/board_test.go +99 -0
- package/internal/board/card.go +72 -0
- package/internal/board/card_test.go +111 -0
- package/internal/board/column.go +61 -0
- package/internal/board/column_test.go +81 -0
- package/internal/board/detail.go +140 -0
- package/internal/board/detail_test.go +233 -0
- package/internal/board/epic_panel.go +69 -0
- package/internal/board/epic_panel_test.go +246 -0
- package/internal/board/help.go +40 -0
- package/internal/board/help_test.go +85 -0
- package/internal/board/layout.go +58 -0
- package/internal/board/layout_test.go +89 -0
- package/internal/board/model.go +151 -0
- package/internal/board/model_test.go +67 -0
- package/internal/board/release.go +42 -0
- package/internal/board/release_test.go +177 -0
- package/internal/board/transitions.go +88 -0
- package/internal/board/transitions_test.go +141 -0
- package/internal/board/update.go +155 -0
- package/internal/board/update_test.go +128 -0
- package/internal/board/view.go +190 -0
- package/internal/board/view_test.go +147 -0
- package/internal/data/config.go +87 -0
- package/internal/data/config_test.go +73 -0
- package/internal/data/discover.go +152 -0
- package/internal/data/discover_test.go +106 -0
- package/internal/data/errors.go +9 -0
- package/internal/data/lifecycle.go +37 -0
- package/internal/data/lifecycle_test.go +38 -0
- package/internal/data/parser.go +189 -0
- package/internal/data/parser_test.go +216 -0
- package/internal/data/router.go +52 -0
- package/internal/data/router_test.go +35 -0
- package/internal/data/task.go +46 -0
- package/internal/data/task_test.go +51 -0
- package/internal/data/write.go +144 -0
- package/internal/data/write_test.go +456 -0
- package/internal/styles/palette.go +47 -0
- package/internal/styles/styles.go +122 -0
- package/main.exe +0 -0
- package/main.go +11 -0
- package/package.json +25 -0
- package/savepoint +0 -0
- package/savepoint.exe +0 -0
- package/scripts/vitest-preload.cjs +95 -0
- package/templates/project/.savepoint/Design.md +47 -0
- package/templates/project/.savepoint/PRD.md +34 -0
- package/templates/project/.savepoint/config.yml +27 -0
- package/templates/project/.savepoint/router.md +152 -0
- package/templates/project/.savepoint/visual-identity.md +122 -0
- package/templates/project/AGENTS.md +130 -0
- package/templates/prompts/audit-reconciliation.prompt.md +67 -0
- package/templates/prompts/design.prompt.md +43 -0
- package/templates/prompts/epic-design.prompt.md +43 -0
- package/templates/prompts/magic-prompt.prompt.md +7 -0
- package/templates/prompts/prd.prompt.md +42 -0
- package/templates/prompts/task-breakdown.prompt.md +54 -0
- package/templates/prompts/task-building.prompt.md +38 -0
- package/templates/prompts/task-planning.prompt.md +53 -0
- package/templates/release/v1/PRD.md +37 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# Ink.js Gotchas & Common Problems
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Icon / Emoji Width Problems](#1-icon--emoji-width-problems)
|
|
6
|
+
2. [Ctrl+C Handling](#2-ctrlc-handling)
|
|
7
|
+
3. [useInput Conflicts](#3-useinput-conflicts)
|
|
8
|
+
4. [Enter Double-Fire](#4-enter-double-fire)
|
|
9
|
+
5. [CJK / Multi-Width Cursor](#5-cjk--multi-width-cursor)
|
|
10
|
+
6. [Layout Breaks](#6-layout-breaks)
|
|
11
|
+
7. [console.log Interference](#7-consolelog-interference)
|
|
12
|
+
8. [Ink Color Type Errors](#8-ink-color-type-errors)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. Icon / Emoji Width Problems
|
|
17
|
+
|
|
18
|
+
### Problem
|
|
19
|
+
|
|
20
|
+
`string-width` v8+ changed how Variation Selectors (VS15/VS16) are handled,
|
|
21
|
+
causing emoji width calculations to be off. Terminals may render an emoji as
|
|
22
|
+
2 cells wide, but `stringWidth()` sometimes returns 1.
|
|
23
|
+
|
|
24
|
+
### Solution: WIDTH_OVERRIDES Pattern
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import stringWidth from "string-width";
|
|
28
|
+
|
|
29
|
+
const iconWidthOverrides: Record<string, number> = {
|
|
30
|
+
"⚡": 1,
|
|
31
|
+
"✨": 1,
|
|
32
|
+
"🐛": 1,
|
|
33
|
+
"🔥": 1,
|
|
34
|
+
"🚀": 1,
|
|
35
|
+
"📌": 1,
|
|
36
|
+
"🟢": 1,
|
|
37
|
+
"🟠": 1,
|
|
38
|
+
"👉": 1,
|
|
39
|
+
"💾": 1,
|
|
40
|
+
"📤": 1,
|
|
41
|
+
"🔃": 1,
|
|
42
|
+
"✅": 1,
|
|
43
|
+
"⚠️": 1,
|
|
44
|
+
"🔗": 1,
|
|
45
|
+
"💻": 1,
|
|
46
|
+
"☁️": 1,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const getIconWidth = (icon: string): number => {
|
|
50
|
+
const baseWidth = stringWidth(icon);
|
|
51
|
+
const override = iconWidthOverrides[icon];
|
|
52
|
+
return override !== undefined ? Math.max(baseWidth, override) : baseWidth;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Fixed-width column padding
|
|
56
|
+
const COLUMN_WIDTH = 2;
|
|
57
|
+
const padIcon = (icon: string): string => {
|
|
58
|
+
const width = getIconWidth(icon);
|
|
59
|
+
const padding = Math.max(0, COLUMN_WIDTH - width);
|
|
60
|
+
return icon + " ".repeat(padding);
|
|
61
|
+
};
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Affected Areas
|
|
65
|
+
|
|
66
|
+
- Status icon columns in board view
|
|
67
|
+
- Progress indicators
|
|
68
|
+
- Any fixed-width layout with inline symbols
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 2. Ctrl+C Handling
|
|
73
|
+
|
|
74
|
+
### Problem
|
|
75
|
+
|
|
76
|
+
Ink handles `Ctrl+C` by default to exit the app. This can fire twice or
|
|
77
|
+
conflict with custom cleanup logic.
|
|
78
|
+
|
|
79
|
+
### Solution: `exitOnCtrlC: false` + `useInput` + SIGINT
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { render, useApp, useInput } from "ink";
|
|
83
|
+
import process from "node:process";
|
|
84
|
+
|
|
85
|
+
function App({ onExit }: { onExit: () => void }) {
|
|
86
|
+
const { exit } = useApp();
|
|
87
|
+
|
|
88
|
+
useInput((input, key) => {
|
|
89
|
+
if (key.ctrl && input === "c") {
|
|
90
|
+
onExit();
|
|
91
|
+
exit();
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Also handle SIGINT directly
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
const handler = () => {
|
|
98
|
+
onExit();
|
|
99
|
+
exit();
|
|
100
|
+
};
|
|
101
|
+
process.on("SIGINT", handler);
|
|
102
|
+
return () => process.off("SIGINT", handler);
|
|
103
|
+
}, [exit, onExit]);
|
|
104
|
+
|
|
105
|
+
return <Box>...</Box>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Disable Ink's default Ctrl+C handler
|
|
109
|
+
render(<App onExit={cleanup} />, { exitOnCtrlC: false });
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 3. useInput Conflicts
|
|
115
|
+
|
|
116
|
+
### Problem
|
|
117
|
+
|
|
118
|
+
When multiple `useInput` hooks exist, **all handlers fire** for every keypress.
|
|
119
|
+
A parent and child component may both process the same key.
|
|
120
|
+
|
|
121
|
+
### Solutions
|
|
122
|
+
|
|
123
|
+
**Pattern 1: `disabled` prop**
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
useInput((input, key) => {
|
|
127
|
+
if (disabled) return; // Ignore input when inactive
|
|
128
|
+
// Handle key...
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Pattern 2: Mode flag**
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
const [filterMode, setFilterMode] = useState(false);
|
|
136
|
+
|
|
137
|
+
useInput((input, key) => {
|
|
138
|
+
if (filterMode) return; // Global shortcuts disabled while filtering
|
|
139
|
+
if (input === "c") onCleanupCommand?.();
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Pattern 3: `blockKeys` prop on Input component**
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// Inside an Input component
|
|
147
|
+
useInput((input) => {
|
|
148
|
+
if (blockKeys && blockKeys.includes(input)) {
|
|
149
|
+
return; // Consume the key; do not propagate to parent
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Usage
|
|
154
|
+
<Input blockKeys={["c", "r", "f"]} ... />
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 4. Enter Double-Fire
|
|
160
|
+
|
|
161
|
+
### Problem
|
|
162
|
+
|
|
163
|
+
After selecting an item in a Select component, the Enter key event propagates
|
|
164
|
+
to the next screen and triggers an unwanted action there.
|
|
165
|
+
|
|
166
|
+
### Solution: Ready-state buffering
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
const [ready, setReady] = useState(false);
|
|
170
|
+
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
// Defer input handling until after the first render cycle
|
|
173
|
+
const timer = setTimeout(() => setReady(true), 50);
|
|
174
|
+
return () => clearTimeout(timer);
|
|
175
|
+
}, []);
|
|
176
|
+
|
|
177
|
+
useInput((input, key) => {
|
|
178
|
+
if (!ready) return; // Drop events during initialization
|
|
179
|
+
if (key.return) {
|
|
180
|
+
onSelect(selectedItem);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 5. CJK / Multi-Width Cursor
|
|
188
|
+
|
|
189
|
+
### Problem
|
|
190
|
+
|
|
191
|
+
CJK characters and some symbols display as 2 cells wide but count as 1
|
|
192
|
+
character. Calculating cursor position by character count causes drift during
|
|
193
|
+
CJK input.
|
|
194
|
+
|
|
195
|
+
### Solution: Display-width based position calculation
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
function getCharWidth(char: string): number {
|
|
199
|
+
const code = char.codePointAt(0);
|
|
200
|
+
if (!code) return 1;
|
|
201
|
+
|
|
202
|
+
// CJK, Hangul, emoji ranges render as 2 cells wide
|
|
203
|
+
if (
|
|
204
|
+
(code >= 0x1100 && code <= 0x115f) || // Hangul Jamo
|
|
205
|
+
(code >= 0x2e80 && code <= 0x9fff) || // CJK
|
|
206
|
+
(code >= 0xac00 && code <= 0xd7af) || // Hangul Syllables
|
|
207
|
+
(code >= 0xf900 && code <= 0xfaff) || // CJK Compatibility
|
|
208
|
+
(code >= 0xfe10 && code <= 0xfe1f) || // Vertical forms
|
|
209
|
+
(code >= 0x1f300 && code <= 0x1f9ff) // Emojis
|
|
210
|
+
) {
|
|
211
|
+
return 2;
|
|
212
|
+
}
|
|
213
|
+
return 1;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function getDisplayWidth(str: string): number {
|
|
217
|
+
return [...str].reduce((width, char) => width + getCharWidth(char), 0);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function toDisplayColumn(text: string, charPosition: number): number {
|
|
221
|
+
return getDisplayWidth(text.slice(0, charPosition));
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## 6. Layout Breaks
|
|
228
|
+
|
|
229
|
+
### Problem
|
|
230
|
+
|
|
231
|
+
- Long lines wrap past terminal width
|
|
232
|
+
- Timestamps or labels get pushed to the next line
|
|
233
|
+
- Column positions drift
|
|
234
|
+
|
|
235
|
+
### Solutions
|
|
236
|
+
|
|
237
|
+
**Safe margin**
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
const { stdout } = useStdout();
|
|
241
|
+
const columns = Math.max(20, (stdout?.columns ?? 80) - 1); // 1-cell margin
|
|
242
|
+
|
|
243
|
+
// Or use 90% width (Gemini CLI pattern)
|
|
244
|
+
const safeColumns = Math.floor(columns * 0.9);
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Fixed-width columns**
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
const COLUMN_WIDTH = 2;
|
|
251
|
+
const padToWidth = (content: string, width: number): string => {
|
|
252
|
+
const actualWidth = stringWidth(content);
|
|
253
|
+
const padding = Math.max(0, width - actualWidth);
|
|
254
|
+
return content + " ".repeat(padding);
|
|
255
|
+
};
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Truncate to width**
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
function truncateToWidth(text: string, maxWidth: number): string {
|
|
262
|
+
let width = 0;
|
|
263
|
+
let result = "";
|
|
264
|
+
for (const char of text) {
|
|
265
|
+
const charWidth = getCharWidth(char);
|
|
266
|
+
if (width + charWidth > maxWidth) {
|
|
267
|
+
return result + "…";
|
|
268
|
+
}
|
|
269
|
+
width += charWidth;
|
|
270
|
+
result += char;
|
|
271
|
+
}
|
|
272
|
+
return result;
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 7. console.log Interference
|
|
279
|
+
|
|
280
|
+
### Problem
|
|
281
|
+
|
|
282
|
+
`console.log` output competes with Ink's rendering. Ink redraws the screen
|
|
283
|
+
periodically, so log lines get overwritten or corrupt the UI.
|
|
284
|
+
|
|
285
|
+
### Solutions
|
|
286
|
+
|
|
287
|
+
**Structured logs to stderr**
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
import pino from "pino";
|
|
291
|
+
|
|
292
|
+
const logger = pino({
|
|
293
|
+
transport: {
|
|
294
|
+
target: "pino-pretty",
|
|
295
|
+
options: { destination: 2 }, // stderr
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Conditional debug logging**
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
if (process.env.DEBUG) {
|
|
304
|
+
console.error("Debug:", data); // stderr, not stdout
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## 8. Ink Color Type Errors
|
|
311
|
+
|
|
312
|
+
### Problem
|
|
313
|
+
|
|
314
|
+
Using `<Text color="cyan">` in TypeScript can trigger strict type errors
|
|
315
|
+
because Ink's color types are narrow.
|
|
316
|
+
|
|
317
|
+
### Solutions
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
// Use a const assertion
|
|
321
|
+
<Text color={"cyan" as const}>...</Text>
|
|
322
|
+
|
|
323
|
+
// Or define as a variable
|
|
324
|
+
const selectedColor = "cyan" as const;
|
|
325
|
+
<Text color={selectedColor}>...</Text>
|
|
326
|
+
|
|
327
|
+
// Or use chalk inside the Text node
|
|
328
|
+
import chalk from "chalk";
|
|
329
|
+
<Text>{chalk.cyan("Selected")}</Text>
|
|
330
|
+
```
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# Testing Patterns & Best Practices
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Test Environment Setup](#test-environment-setup)
|
|
6
|
+
2. [Component Tests](#component-tests)
|
|
7
|
+
3. [Mock Patterns](#mock-patterns)
|
|
8
|
+
4. [Integration Tests](#integration-tests)
|
|
9
|
+
5. [Edge Cases](#edge-cases)
|
|
10
|
+
6. [Performance Tests](#performance-tests)
|
|
11
|
+
7. [Test Utilities](#test-utilities)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Test Environment Setup
|
|
16
|
+
|
|
17
|
+
### Vitest + happy-dom
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
/**
|
|
21
|
+
* @vitest-environment happy-dom
|
|
22
|
+
*/
|
|
23
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
24
|
+
import { render } from "@testing-library/react";
|
|
25
|
+
import { Window } from "happy-dom";
|
|
26
|
+
|
|
27
|
+
describe("Component", () => {
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
const window = new Window();
|
|
30
|
+
// @ts-expect-error - happy-dom type mismatch
|
|
31
|
+
globalThis.window = window;
|
|
32
|
+
// @ts-expect-error - happy-dom type mismatch
|
|
33
|
+
globalThis.document = window.document;
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### ink-testing-library
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { render } from "ink-testing-library";
|
|
42
|
+
|
|
43
|
+
describe("Select", () => {
|
|
44
|
+
it("should render items", () => {
|
|
45
|
+
const items = [
|
|
46
|
+
{ label: "Item 1", value: "1" },
|
|
47
|
+
{ label: "Item 2", value: "2" },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const { lastFrame } = render(
|
|
51
|
+
<Select items={items} onSelect={vi.fn()} />,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
expect(lastFrame()).toContain("Item 1");
|
|
55
|
+
expect(lastFrame()).toContain("Item 2");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Component Tests
|
|
63
|
+
|
|
64
|
+
### Basic Pattern
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { render } from "ink-testing-library";
|
|
68
|
+
import { describe, it, expect, vi } from "vitest";
|
|
69
|
+
|
|
70
|
+
describe("Header", () => {
|
|
71
|
+
it("should render title", () => {
|
|
72
|
+
const { lastFrame } = render(<Header title="Test" />);
|
|
73
|
+
expect(lastFrame()).toContain("Test");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should render version when provided", () => {
|
|
77
|
+
const { lastFrame } = render(
|
|
78
|
+
<Header title="Test" version="1.0.0" />,
|
|
79
|
+
);
|
|
80
|
+
expect(lastFrame()).toContain("1.0.0");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Key Input Tests
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { render } from "ink-testing-library";
|
|
89
|
+
|
|
90
|
+
describe("Select", () => {
|
|
91
|
+
it("should move selection down on j key", async () => {
|
|
92
|
+
const items = [
|
|
93
|
+
{ label: "Item 1", value: "1" },
|
|
94
|
+
{ label: "Item 2", value: "2" },
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
const { stdin, lastFrame } = render(
|
|
98
|
+
<Select items={items} onSelect={vi.fn()} />,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Initial state
|
|
102
|
+
expect(lastFrame()).toContain("› Item 1");
|
|
103
|
+
|
|
104
|
+
// Press j to move down
|
|
105
|
+
stdin.write("j");
|
|
106
|
+
|
|
107
|
+
// Selection moved
|
|
108
|
+
expect(lastFrame()).toContain("› Item 2");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should call onSelect on Enter", () => {
|
|
112
|
+
const onSelect = vi.fn();
|
|
113
|
+
const items = [{ label: "Item 1", value: "1" }];
|
|
114
|
+
|
|
115
|
+
const { stdin } = render(
|
|
116
|
+
<Select items={items} onSelect={onSelect} />,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
stdin.write("\r"); // Enter key
|
|
120
|
+
|
|
121
|
+
expect(onSelect).toHaveBeenCalledWith(items[0]);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Async Tests
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { render } from "ink-testing-library";
|
|
130
|
+
import { vi } from "vitest";
|
|
131
|
+
|
|
132
|
+
describe("LoadingIndicator", () => {
|
|
133
|
+
beforeEach(() => {
|
|
134
|
+
vi.useFakeTimers();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
afterEach(() => {
|
|
138
|
+
vi.useRealTimers();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should show after delay", async () => {
|
|
142
|
+
const { lastFrame } = render(
|
|
143
|
+
<LoadingIndicator isLoading={true} delay={300} />,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// Not shown initially
|
|
147
|
+
expect(lastFrame()).toBe("");
|
|
148
|
+
|
|
149
|
+
// Shown after 300ms
|
|
150
|
+
vi.advanceTimersByTime(300);
|
|
151
|
+
expect(lastFrame()).toContain("Loading");
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Mock Patterns
|
|
159
|
+
|
|
160
|
+
### External Dependency Mock
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
vi.mock("../../../fs/project.js", () => ({
|
|
164
|
+
findProjectRoot: vi.fn().mockResolvedValue("/repo"),
|
|
165
|
+
readConfig: vi.fn().mockResolvedValue({ defaultRelease: "v1" }),
|
|
166
|
+
getSavepointPath: vi.fn().mockReturnValue("/repo/.savepoint"),
|
|
167
|
+
}));
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Screen Component Mock
|
|
171
|
+
|
|
172
|
+
Mock sibling screens to isolate the component under test:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
const capturedProps: BoardScreenProps[] = [];
|
|
176
|
+
|
|
177
|
+
vi.mock("../../components/screens/BoardScreen.js", () => ({
|
|
178
|
+
BoardScreen: (props: BoardScreenProps) => {
|
|
179
|
+
capturedProps.push(props);
|
|
180
|
+
return <div>Mocked BoardScreen</div>;
|
|
181
|
+
},
|
|
182
|
+
}));
|
|
183
|
+
|
|
184
|
+
describe("App", () => {
|
|
185
|
+
beforeEach(() => {
|
|
186
|
+
capturedProps.length = 0; // Reset
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("should pass tasks to BoardScreen", async () => {
|
|
190
|
+
render(<App />);
|
|
191
|
+
|
|
192
|
+
await waitFor(() => {
|
|
193
|
+
expect(capturedProps.length).toBeGreaterThan(0);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
expect(capturedProps[0].tasks).toHaveLength(2);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Hook Mock
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
vi.mock("../../hooks/useAsyncData.js", () => ({
|
|
205
|
+
useAsyncData: () => ({
|
|
206
|
+
data: [
|
|
207
|
+
{ id: "T001", title: "Setup project" },
|
|
208
|
+
{ id: "T002", title: "Add tests" },
|
|
209
|
+
],
|
|
210
|
+
loading: false,
|
|
211
|
+
error: null,
|
|
212
|
+
refresh: vi.fn(),
|
|
213
|
+
}),
|
|
214
|
+
}));
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Conditional Mock Responses
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
const mockFetchTasks = vi.fn();
|
|
221
|
+
|
|
222
|
+
vi.mock("../../../domain/tasks.js", () => ({
|
|
223
|
+
fetchTasks: mockFetchTasks,
|
|
224
|
+
}));
|
|
225
|
+
|
|
226
|
+
describe("useAsyncData", () => {
|
|
227
|
+
it("should handle error", async () => {
|
|
228
|
+
mockFetchTasks.mockRejectedValueOnce(new Error("Read error"));
|
|
229
|
+
// Assert error state is set
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("should return tasks", async () => {
|
|
233
|
+
mockFetchTasks.mockResolvedValueOnce([{ id: "T001" }]);
|
|
234
|
+
// Assert data is returned
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Integration Tests
|
|
242
|
+
|
|
243
|
+
### Navigation Flow
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
describe("Navigation", () => {
|
|
247
|
+
it("should navigate from board to detail on Enter", async () => {
|
|
248
|
+
const { stdin, lastFrame } = render(<App />);
|
|
249
|
+
|
|
250
|
+
// Wait for board
|
|
251
|
+
await waitFor(() => {
|
|
252
|
+
expect(lastFrame()).toContain("Board");
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Press Enter
|
|
256
|
+
stdin.write("\r");
|
|
257
|
+
|
|
258
|
+
// Detail screen appears
|
|
259
|
+
await waitFor(() => {
|
|
260
|
+
expect(lastFrame()).toContain("Detail");
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("should go back on Escape", async () => {
|
|
265
|
+
const { stdin, lastFrame } = render(<App />);
|
|
266
|
+
|
|
267
|
+
// Navigate to detail
|
|
268
|
+
stdin.write("\r");
|
|
269
|
+
await waitFor(() => {
|
|
270
|
+
expect(lastFrame()).toContain("Detail");
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Press Escape
|
|
274
|
+
stdin.write("\x1B"); // Escape key
|
|
275
|
+
|
|
276
|
+
await waitFor(() => {
|
|
277
|
+
expect(lastFrame()).toContain("Board");
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Edge Cases
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
describe("Edge Cases", () => {
|
|
289
|
+
it("should handle empty list", () => {
|
|
290
|
+
const { lastFrame } = render(
|
|
291
|
+
<Select items={[]} onSelect={vi.fn()} />,
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
expect(lastFrame()).not.toContain("undefined");
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("should handle very long labels", () => {
|
|
298
|
+
const items = [{
|
|
299
|
+
label: "A".repeat(200),
|
|
300
|
+
value: "1",
|
|
301
|
+
}];
|
|
302
|
+
|
|
303
|
+
const { lastFrame } = render(
|
|
304
|
+
<Select items={items} onSelect={vi.fn()} />,
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
// Assert output is defined (not crashed)
|
|
308
|
+
expect(lastFrame()).toBeDefined();
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Performance Tests
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
describe("Performance", () => {
|
|
319
|
+
it("should handle 1000 items without significant lag", async () => {
|
|
320
|
+
const items = Array.from({ length: 1000 }, (_, i) => ({
|
|
321
|
+
label: `Item ${i}`,
|
|
322
|
+
value: String(i),
|
|
323
|
+
}));
|
|
324
|
+
|
|
325
|
+
const start = performance.now();
|
|
326
|
+
|
|
327
|
+
const { stdin } = render(
|
|
328
|
+
<Select items={items} onSelect={vi.fn()} limit={20} />,
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
// 100 navigation keypresses
|
|
332
|
+
for (let i = 0; i < 100; i++) {
|
|
333
|
+
stdin.write("j");
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const duration = performance.now() - start;
|
|
337
|
+
|
|
338
|
+
// Must complete within 1 second
|
|
339
|
+
expect(duration).toBeLessThan(1000);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Test Utilities
|
|
347
|
+
|
|
348
|
+
### waitFor Helper
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
async function waitFor(
|
|
352
|
+
condition: () => boolean | void,
|
|
353
|
+
timeout = 1000,
|
|
354
|
+
): Promise<void> {
|
|
355
|
+
const start = Date.now();
|
|
356
|
+
|
|
357
|
+
while (Date.now() - start < timeout) {
|
|
358
|
+
try {
|
|
359
|
+
if (condition()) return;
|
|
360
|
+
} catch {
|
|
361
|
+
// Condition not yet met
|
|
362
|
+
}
|
|
363
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
throw new Error("waitFor timed out");
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Key Constants
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
const KEYS = {
|
|
374
|
+
ENTER: "\r",
|
|
375
|
+
ESCAPE: "\x1B",
|
|
376
|
+
UP: "\x1B[A",
|
|
377
|
+
DOWN: "\x1B[B",
|
|
378
|
+
LEFT: "\x1B[D",
|
|
379
|
+
RIGHT: "\x1B[C",
|
|
380
|
+
TAB: "\t",
|
|
381
|
+
BACKSPACE: "\x7F",
|
|
382
|
+
CTRL_C: "\x03",
|
|
383
|
+
};
|
|
384
|
+
```
|