diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 489b51f..34ff708 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -106,11 +106,13 @@ Plans:
3. Pipeline fails before Docker build when unit tests fail
4. Pipeline fails before Docker build when type checking fails
5. E2E tests run in pipeline using Playwright Docker image
-**Plans**: TBD
+**Plans**: 4 plans
Plans:
-- [ ] 09-01: Vitest setup and unit test structure
-- [ ] 09-02: Pipeline integration with fail-fast behavior
+- [ ] 09-01-PLAN.md — Test infrastructure setup (Vitest + browser mode)
+- [ ] 09-02-PLAN.md — Unit and component test suite with coverage
+- [ ] 09-03-PLAN.md — E2E test suite with database fixtures
+- [ ] 09-04-PLAN.md — CI pipeline integration with fail-fast behavior
## Progress
@@ -127,13 +129,14 @@ Phases execute in numeric order: 7 -> 8 -> 9
| 6. Deployment | v1.0 | 2/2 | Complete | 2026-02-01 |
| 7. GitOps Foundation | v2.0 | 2/2 | Complete ✓ | 2026-02-03 |
| 8. Observability Stack | v2.0 | 0/3 | Planned | - |
-| 9. CI Pipeline Hardening | v2.0 | 0/2 | Not started | - |
+| 9. CI Pipeline Hardening | v2.0 | 0/4 | Planned | - |
---
*Roadmap created: 2026-01-29*
*v2.0 phases added: 2026-02-03*
*Phase 7 planned: 2026-02-03*
*Phase 8 planned: 2026-02-03*
+*Phase 9 planned: 2026-02-03*
*Depth: standard*
*v1.0 Coverage: 31/31 requirements mapped*
*v2.0 Coverage: 17/17 requirements mapped*
diff --git a/.planning/phases/09-ci-pipeline/09-01-PLAN.md b/.planning/phases/09-ci-pipeline/09-01-PLAN.md
new file mode 100644
index 0000000..2630172
--- /dev/null
+++ b/.planning/phases/09-ci-pipeline/09-01-PLAN.md
@@ -0,0 +1,182 @@
+---
+phase: 09-ci-pipeline
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - package.json
+ - vite.config.ts
+ - vitest-setup-client.ts
+ - src/lib/utils/filterEntries.test.ts
+autonomous: true
+
+must_haves:
+ truths:
+ - "npm run test:unit executes Vitest and reports pass/fail"
+ - "Vitest browser mode runs component tests in real Chromium"
+ - "Vitest node mode runs server/utility tests"
+ - "SvelteKit modules ($app/*) are mocked in test environment"
+ artifacts:
+ - path: "vite.config.ts"
+ provides: "Multi-project Vitest configuration"
+ contains: "projects:"
+ - path: "vitest-setup-client.ts"
+ provides: "SvelteKit module mocks for browser tests"
+ contains: "vi.mock('$app/"
+ - path: "package.json"
+ provides: "Test scripts"
+ contains: "test:unit"
+ - path: "src/lib/utils/filterEntries.test.ts"
+ provides: "Sample unit test proving setup works"
+ min_lines: 15
+ key_links:
+ - from: "vite.config.ts"
+ to: "vitest-setup-client.ts"
+ via: "setupFiles configuration"
+ pattern: "setupFiles.*vitest-setup"
+---
+
+
+Configure Vitest test infrastructure with multi-project setup for SvelteKit.
+
+Purpose: Establish the test runner foundation that all subsequent test plans build upon. This enables unit tests (node mode) and component tests (browser mode) with proper SvelteKit module mocking.
+
+Output: Working Vitest configuration with browser mode for Svelte 5 components and node mode for server code, plus a sample test proving the setup works.
+
+
+
+@/home/tho/.claude/get-shit-done/workflows/execute-plan.md
+@/home/tho/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/phases/09-ci-pipeline/09-RESEARCH.md
+
+@vite.config.ts
+@package.json
+@playwright.config.ts
+
+
+
+
+
+ Task 1: Install Vitest dependencies and configure multi-project setup
+ package.json, vite.config.ts
+
+Install Vitest and browser mode dependencies:
+```bash
+npm install -D vitest @vitest/browser vitest-browser-svelte @vitest/browser-playwright @vitest/coverage-v8
+npx playwright install chromium
+```
+
+Update vite.config.ts with multi-project Vitest configuration:
+- Import `playwright` from `@vitest/browser-playwright`
+- Add `test` config with `coverage` (provider: v8, include src/**/*, thresholds with autoUpdate: true initially)
+- Configure two projects:
+ 1. `client`: browser mode with Playwright provider, include `*.svelte.{test,spec}.ts`, setupFiles pointing to vitest-setup-client.ts
+ 2. `server`: node environment, include `*.{test,spec}.ts`, exclude `*.svelte.{test,spec}.ts`
+
+Update package.json scripts:
+- Add `"test": "vitest"`
+- Add `"test:unit": "vitest run"`
+- Add `"test:unit:watch": "vitest"`
+- Add `"test:coverage": "vitest run --coverage"`
+
+Keep existing scripts (test:e2e, test:e2e:docker) unchanged.
+
+
+Run `npm run test:unit` - should execute (may show "no tests found" initially, but Vitest runs without config errors)
+Run `npx vitest --version` - confirms Vitest is installed
+
+ Vitest installed with multi-project config. npm run test:unit executes without configuration errors.
+
+
+
+ Task 2: Create SvelteKit module mocks in setup file
+ vitest-setup-client.ts
+
+Create vitest-setup-client.ts in project root with:
+
+1. Add TypeScript reference directives:
+ - `/// `
+ - `/// `
+
+2. Mock `$app/navigation`:
+ - goto: vi.fn returning Promise.resolve()
+ - invalidate: vi.fn returning Promise.resolve()
+ - invalidateAll: vi.fn returning Promise.resolve()
+ - beforeNavigate: vi.fn()
+ - afterNavigate: vi.fn()
+
+3. Mock `$app/stores`:
+ - page: writable store with URL, params, route, status, error, data, form
+ - navigating: writable(null)
+ - updated: { check: vi.fn(), subscribe: writable(false).subscribe }
+
+4. Mock `$app/environment`:
+ - browser: true
+ - dev: true
+ - building: false
+
+Import writable from 'svelte/store' and vi from 'vitest'.
+
+Note: Use simple mocks, do NOT use importOriginal with SvelteKit modules (causes SSR issues per research).
+
+
+File exists at vitest-setup-client.ts with all required mocks.
+TypeScript compilation succeeds: `npx tsc --noEmit vitest-setup-client.ts` (or no TS errors shown in editor)
+
+ SvelteKit module mocks created. Browser-mode tests can import $app/* without errors.
+
+
+
+ Task 3: Write sample test to verify infrastructure
+ src/lib/utils/filterEntries.test.ts
+
+Create src/lib/utils/filterEntries.test.ts as a node-mode unit test:
+
+1. Import { describe, it, expect } from 'vitest'
+2. Import filterEntries function from './filterEntries'
+3. Read filterEntries.ts to understand the function signature and behavior
+
+Write tests for filterEntries covering:
+- Empty entries array returns empty array
+- Filter by tag returns matching entries
+- Filter by search term matches title/content
+- Combined filters (tag + search) work together
+- Type filter (task vs thought) works if applicable
+
+This proves the server/node project runs correctly.
+
+Note: This is a real test, not just a placeholder. Aim for thorough coverage of filterEntries.ts functionality.
+
+
+Run `npm run test:unit` - filterEntries tests execute and pass
+Run `npm run test:coverage` - shows coverage report including filterEntries.ts
+
+ Sample unit test passes. Vitest infrastructure is verified working for node-mode tests.
+
+
+
+
+
+1. `npm run test:unit` executes without errors
+2. `npm run test:coverage` produces coverage report
+3. filterEntries.test.ts tests pass
+4. vite.config.ts contains multi-project test configuration
+5. vitest-setup-client.ts contains $app/* mocks
+
+
+
+- CI-01 requirement satisfied: Vitest installed and configured
+- Multi-project setup distinguishes client (browser) and server (node) tests
+- At least one unit test passes proving the infrastructure works
+- Coverage reporting functional (threshold enforcement comes in Plan 02)
+
+
+
diff --git a/.planning/phases/09-ci-pipeline/09-02-PLAN.md b/.planning/phases/09-ci-pipeline/09-02-PLAN.md
new file mode 100644
index 0000000..8030956
--- /dev/null
+++ b/.planning/phases/09-ci-pipeline/09-02-PLAN.md
@@ -0,0 +1,211 @@
+---
+phase: 09-ci-pipeline
+plan: 02
+type: execute
+wave: 2
+depends_on: ["09-01"]
+files_modified:
+ - src/lib/utils/highlightText.test.ts
+ - src/lib/utils/parseHashtags.test.ts
+ - src/lib/components/SearchBar.svelte.test.ts
+ - src/lib/components/TagInput.svelte.test.ts
+ - src/lib/components/CompletedToggle.svelte.test.ts
+ - vite.config.ts
+autonomous: true
+
+must_haves:
+ truths:
+ - "All utility functions have passing tests"
+ - "Component tests run in real browser via Vitest browser mode"
+ - "Coverage threshold is enforced (starts with autoUpdate baseline)"
+ artifacts:
+ - path: "src/lib/utils/highlightText.test.ts"
+ provides: "Tests for text highlighting utility"
+ min_lines: 20
+ - path: "src/lib/utils/parseHashtags.test.ts"
+ provides: "Tests for hashtag parsing utility"
+ min_lines: 20
+ - path: "src/lib/components/SearchBar.svelte.test.ts"
+ provides: "Browser-mode test for SearchBar component"
+ min_lines: 25
+ - path: "src/lib/components/TagInput.svelte.test.ts"
+ provides: "Browser-mode test for TagInput component"
+ min_lines: 25
+ - path: "src/lib/components/CompletedToggle.svelte.test.ts"
+ provides: "Browser-mode test for toggle component"
+ min_lines: 20
+ key_links:
+ - from: "src/lib/components/SearchBar.svelte.test.ts"
+ to: "vitest-browser-svelte"
+ via: "render import"
+ pattern: "import.*render.*from.*vitest-browser-svelte"
+---
+
+
+Write unit tests for utility functions and initial component tests to establish testing patterns.
+
+Purpose: Create comprehensive tests for pure utility functions (easy wins for coverage) and establish the component testing pattern using Vitest browser mode. This proves both test project configurations work.
+
+Output: All utility functions tested, 3 component tests demonstrating the browser-mode pattern, coverage baseline established.
+
+
+
+@/home/tho/.claude/get-shit-done/workflows/execute-plan.md
+@/home/tho/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/phases/09-ci-pipeline/09-RESEARCH.md
+@.planning/phases/09-ci-pipeline/09-01-SUMMARY.md
+
+@src/lib/utils/highlightText.ts
+@src/lib/utils/parseHashtags.ts
+@src/lib/components/SearchBar.svelte
+@src/lib/components/TagInput.svelte
+@src/lib/components/CompletedToggle.svelte
+@vitest-setup-client.ts
+
+
+
+
+
+ Task 1: Write unit tests for remaining utility functions
+ src/lib/utils/highlightText.test.ts, src/lib/utils/parseHashtags.test.ts
+
+Read each utility file to understand its behavior, then write comprehensive tests:
+
+**highlightText.test.ts:**
+- Import function and test utilities from vitest
+- Test: Returns original text when no search term
+- Test: Highlights single match with mark tag
+- Test: Highlights multiple matches
+- Test: Case-insensitive matching
+- Test: Handles special regex characters in search term
+- Test: Returns empty string for empty input
+
+**parseHashtags.test.ts:**
+- Import function and test utilities from vitest
+- Test: Extracts single hashtag from text
+- Test: Extracts multiple hashtags
+- Test: Returns empty array when no hashtags
+- Test: Handles hashtags at start/middle/end of text
+- Test: Ignores invalid hashtag patterns (e.g., # alone, #123)
+- Test: Removes duplicates if function does that
+
+Each test file should have describe block with descriptive test names.
+Use `it.each` for data-driven tests where appropriate.
+
+
+Run `npm run test:unit -- --reporter=verbose` - all utility tests pass
+Run `npm run test:coverage` - shows improved coverage for src/lib/utils/
+
+ All 3 utility functions (filterEntries, highlightText, parseHashtags) have comprehensive test coverage.
+
+
+
+ Task 2: Write browser-mode component tests for 3 simpler components
+ src/lib/components/SearchBar.svelte.test.ts, src/lib/components/TagInput.svelte.test.ts, src/lib/components/CompletedToggle.svelte.test.ts
+
+Create .svelte.test.ts files (note: .svelte.test.ts NOT .test.ts for browser mode) for three simpler components.
+
+**Pattern for all component tests:**
+```typescript
+import { render } from 'vitest-browser-svelte';
+import { page } from '@vitest/browser/context';
+import { describe, expect, it } from 'vitest';
+import ComponentName from './ComponentName.svelte';
+```
+
+**SearchBar.svelte.test.ts:**
+- Read SearchBar.svelte to understand props and behavior
+- Test: Renders input element
+- Test: Calls onSearch callback when user types (if applicable)
+- Test: Shows clear button when text entered (if applicable)
+- Test: Placeholder text is visible
+
+**TagInput.svelte.test.ts:**
+- Read TagInput.svelte to understand props and behavior
+- Test: Renders tag input element
+- Test: Can add a tag (simulate user typing and pressing enter/adding)
+- Test: Displays existing tags if passed as prop
+
+**CompletedToggle.svelte.test.ts:**
+- Read CompletedToggle.svelte to understand props
+- Test: Renders toggle in unchecked state by default
+- Test: Toggle state changes on click
+- Test: Calls callback when toggled (if applicable)
+
+Use `page.getByRole()`, `page.getByText()`, `page.getByPlaceholder()` for element selection.
+Use `await button.click()` for interactions.
+Use `flushSync()` from 'svelte' after external state changes if needed.
+Use `await expect.element(locator).toBeInTheDocument()` for assertions.
+
+
+Run `npm run test:unit` - component tests run in browser mode (you'll see Chromium launch)
+All 3 component tests pass
+
+ Browser-mode component testing pattern established with 3 working tests.
+
+
+
+ Task 3: Configure coverage thresholds with baseline
+ vite.config.ts
+
+Update vite.config.ts coverage configuration:
+
+1. Set initial thresholds using autoUpdate to establish baseline:
+```typescript
+thresholds: {
+ autoUpdate: true, // Will update thresholds based on current coverage
+}
+```
+
+2. Run `npm run test:coverage` once to establish baseline thresholds
+
+3. Review the auto-updated thresholds in vite.config.ts
+
+4. If coverage is already above 30%, manually set thresholds to a reasonable starting point (e.g., 50% of current coverage) with a path toward 80%:
+```typescript
+thresholds: {
+ global: {
+ statements: [current - 10],
+ branches: [current - 10],
+ functions: [current - 10],
+ lines: [current - 10],
+ },
+}
+```
+
+5. Add comment noting the target is 80% coverage (CI-01 decision)
+
+Note: Full 80% coverage will be achieved incrementally. This plan establishes the enforcement mechanism.
+
+
+Run `npm run test:coverage` - shows coverage percentages
+Coverage thresholds are set in vite.config.ts
+Future test runs will fail if coverage drops below threshold
+
+ Coverage thresholds configured. Enforcement mechanism in place for incremental coverage improvement.
+
+
+
+
+
+1. `npm run test:unit` runs all tests (utility + component)
+2. Component tests run in Chromium browser (browser mode working)
+3. `npm run test:coverage` shows coverage for utilities and tested components
+4. Coverage thresholds are configured in vite.config.ts
+5. All tests pass
+
+
+
+- All 3 utility functions have comprehensive tests
+- 3 component tests demonstrate browser-mode testing pattern
+- Coverage thresholds configured (starting point toward 80% goal)
+- Both Vitest projects (client browser, server node) verified working
+
+
+
diff --git a/.planning/phases/09-ci-pipeline/09-03-PLAN.md b/.planning/phases/09-ci-pipeline/09-03-PLAN.md
new file mode 100644
index 0000000..53db962
--- /dev/null
+++ b/.planning/phases/09-ci-pipeline/09-03-PLAN.md
@@ -0,0 +1,219 @@
+---
+phase: 09-ci-pipeline
+plan: 03
+type: execute
+wave: 2
+depends_on: ["09-01"]
+files_modified:
+ - playwright.config.ts
+ - tests/e2e/fixtures/db.ts
+ - tests/e2e/user-journeys.spec.ts
+ - tests/e2e/index.ts
+autonomous: true
+
+must_haves:
+ truths:
+ - "E2E tests run against the application with seeded test data"
+ - "User journeys cover create, edit, search, organize, and delete workflows"
+ - "Tests run on both desktop and mobile viewports"
+ - "Screenshots are captured on test failure"
+ artifacts:
+ - path: "playwright.config.ts"
+ provides: "E2E configuration with multi-viewport and screenshot settings"
+ contains: "screenshot: 'only-on-failure'"
+ - path: "tests/e2e/fixtures/db.ts"
+ provides: "Database seeding fixture using drizzle-seed"
+ contains: "drizzle-seed"
+ - path: "tests/e2e/user-journeys.spec.ts"
+ provides: "Core user journey E2E tests"
+ min_lines: 100
+ - path: "tests/e2e/index.ts"
+ provides: "Custom test function with fixtures"
+ contains: "base.extend"
+ key_links:
+ - from: "tests/e2e/user-journeys.spec.ts"
+ to: "tests/e2e/fixtures/db.ts"
+ via: "test import with seededDb fixture"
+ pattern: "import.*test.*from.*fixtures"
+---
+
+
+Create comprehensive E2E test suite with database fixtures and multi-viewport testing.
+
+Purpose: Establish E2E tests that verify full user journeys work correctly. These tests catch integration issues that unit tests miss and provide confidence that the deployed application works as expected.
+
+Output: E2E test suite covering core user workflows, database seeding fixture for consistent test data, multi-viewport testing for desktop and mobile.
+
+
+
+@/home/tho/.claude/get-shit-done/workflows/execute-plan.md
+@/home/tho/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/phases/09-ci-pipeline/09-RESEARCH.md
+@.planning/phases/09-ci-pipeline/09-01-SUMMARY.md
+
+@playwright.config.ts
+@tests/docker-deployment.spec.ts
+@src/lib/server/db/schema.ts
+@src/routes/+page.svelte
+
+
+
+
+
+ Task 1: Update Playwright configuration for E2E requirements
+ playwright.config.ts
+
+Update playwright.config.ts with E2E requirements from user decisions:
+
+1. Set `testDir: './tests/e2e'` (separate from existing docker test)
+2. Set `fullyParallel: false` (shared database)
+3. Set `workers: 1` (avoid database race conditions)
+4. Configure `reporter`:
+ - `['html', { open: 'never' }]`
+ - `['github']` for CI annotations
+
+5. Configure `use`:
+ - `baseURL: process.env.BASE_URL || 'http://localhost:5173'`
+ - `trace: 'on-first-retry'`
+ - `screenshot: 'only-on-failure'` (per user decision: screenshots, no video)
+ - `video: 'off'`
+
+6. Add two projects:
+ - `chromium-desktop`: using `devices['Desktop Chrome']`
+ - `chromium-mobile`: using `devices['Pixel 5']`
+
+7. Configure `webServer`:
+ - `command: 'npm run build && npm run preview'`
+ - `port: 4173`
+ - `reuseExistingServer: !process.env.CI`
+
+Move existing docker-deployment.spec.ts to tests/e2e/ or keep in tests/ with separate config.
+
+
+Run `npx playwright test --list` - shows test files found
+Configuration is valid (no syntax errors)
+
+ Playwright configured for E2E with desktop/mobile viewports, screenshots on failure, single worker for database safety.
+
+
+
+ Task 2: Create database seeding fixture
+ tests/e2e/fixtures/db.ts, tests/e2e/index.ts
+
+First, install drizzle-seed:
+```bash
+npm install -D drizzle-seed
+```
+
+Create tests/e2e/fixtures/db.ts:
+1. Import test base from @playwright/test
+2. Import db from src/lib/server/db
+3. Import schema from src/lib/server/db/schema
+4. Import seed and reset from drizzle-seed
+
+Create a fixture that:
+- Seeds database with known test data before test
+- Provides seeded entries (tasks, thoughts) with predictable IDs and content
+- Cleans up after test using reset()
+
+Create tests/e2e/index.ts:
+- Re-export extended test with seededDb fixture
+- Re-export expect from @playwright/test
+
+Test data should include:
+- At least 5 entries with various states (tasks vs thoughts, completed vs pending)
+- Entries with tags for testing filter/search
+- Entries with images (if applicable to schema)
+- Entries with different dates for sorting tests
+
+Note: Read the actual schema.ts to understand the exact model structure before writing seed logic.
+
+
+TypeScript compiles without errors
+Fixture can be imported in test file
+
+ Database fixture created. Tests can import { test, expect } from './fixtures' to get seeded database.
+
+
+
+ Task 3: Write E2E tests for core user journeys
+ tests/e2e/user-journeys.spec.ts
+
+Create tests/e2e/user-journeys.spec.ts using the custom test with fixtures:
+
+```typescript
+import { test, expect } from './index';
+```
+
+Write tests for each user journey (per CONTEXT.md decisions):
+
+**Create workflow:**
+- Navigate to home page
+- Use quick capture to create a new entry
+- Verify entry appears in list
+- Verify entry persists after page reload
+
+**Edit workflow:**
+- Find an existing entry (from seeded data)
+- Click to open/edit
+- Modify content
+- Save changes
+- Verify changes persisted
+
+**Search workflow:**
+- Use search bar to find entry by text
+- Verify matching entries shown
+- Verify non-matching entries hidden
+- Test search with tags filter
+
+**Organize workflow:**
+- Add tag to entry
+- Filter by tag
+- Verify filtered results
+- Pin an entry (if applicable)
+- Verify pinned entry appears first
+
+**Delete workflow:**
+- Select an entry
+- Delete it
+- Verify entry removed from list
+- Verify entry not found after reload
+
+Use `test.describe()` to group related tests.
+Each test should use `seededDb` fixture for consistent starting state.
+Use page object pattern if tests get complex (optional - can keep simple for now).
+
+
+Run `npm run test:e2e` with app running locally (or let webServer start it)
+All E2E tests pass
+Screenshots are generated in test-results/ for any failures
+
+ E2E test suite covers all core user journeys. Tests run on both desktop and mobile viewports.
+
+
+
+
+
+1. `npm run test:e2e` executes E2E tests
+2. Tests run on both chromium-desktop and chromium-mobile projects
+3. Database is seeded with test data before each test
+4. All 5 user journeys (create, edit, search, organize, delete) have tests
+5. Screenshots captured on failure (can test by making a test fail temporarily)
+6. Tests pass consistently (no flaky tests)
+
+
+
+- CI-04 requirement satisfied: E2E tests ready for pipeline
+- User journeys cover create/edit/search/organize/delete as specified in CONTEXT.md
+- Multi-viewport testing (desktop + mobile) per CONTEXT.md decision
+- Database fixtures provide consistent, isolated test data
+- Screenshot on failure configured (no video per CONTEXT.md decision)
+
+
+
diff --git a/.planning/phases/09-ci-pipeline/09-04-PLAN.md b/.planning/phases/09-ci-pipeline/09-04-PLAN.md
new file mode 100644
index 0000000..31ac613
--- /dev/null
+++ b/.planning/phases/09-ci-pipeline/09-04-PLAN.md
@@ -0,0 +1,218 @@
+---
+phase: 09-ci-pipeline
+plan: 04
+type: execute
+wave: 3
+depends_on: ["09-02", "09-03"]
+files_modified:
+ - .gitea/workflows/build.yaml
+autonomous: false
+
+user_setup:
+ - service: slack
+ why: "Pipeline failure notifications"
+ env_vars:
+ - name: SLACK_WEBHOOK_URL
+ source: "Slack App settings -> Incoming Webhooks -> Create new webhook -> Copy URL"
+ dashboard_config:
+ - task: "Create Slack app with incoming webhook"
+ location: "https://api.slack.com/apps -> Create New App -> From scratch -> Add Incoming Webhooks"
+
+must_haves:
+ truths:
+ - "Pipeline runs type checking before Docker build"
+ - "Pipeline runs unit tests with coverage before Docker build"
+ - "Pipeline runs E2E tests before Docker build"
+ - "Pipeline fails fast when tests or type checking fail"
+ - "Slack notification sent on pipeline failure"
+ - "Test artifacts (coverage, playwright report) are uploaded"
+ artifacts:
+ - path: ".gitea/workflows/build.yaml"
+ provides: "CI pipeline with test jobs"
+ contains: "npm run check"
+ - path: ".gitea/workflows/build.yaml"
+ provides: "Unit test step"
+ contains: "npm run test:coverage"
+ - path: ".gitea/workflows/build.yaml"
+ provides: "E2E test step"
+ contains: "npm run test:e2e"
+ key_links:
+ - from: ".gitea/workflows/build.yaml"
+ to: "package.json scripts"
+ via: "npm run commands"
+ pattern: "npm run (check|test:coverage|test:e2e)"
+ - from: "build job"
+ to: "test job"
+ via: "needs: test"
+ pattern: "needs:\\s*test"
+---
+
+
+Integrate tests into Gitea Actions pipeline with fail-fast behavior and Slack notifications.
+
+Purpose: Ensure tests run automatically on every push/PR and block deployment when tests fail. This is the final piece that makes the test infrastructure actually protect production.
+
+Output: Updated CI workflow with test job that runs before build, fail-fast on errors, and Slack notification on failure.
+
+
+
+@/home/tho/.claude/get-shit-done/workflows/execute-plan.md
+@/home/tho/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/phases/09-ci-pipeline/09-RESEARCH.md
+@.planning/phases/09-ci-pipeline/09-02-SUMMARY.md
+@.planning/phases/09-ci-pipeline/09-03-SUMMARY.md
+
+@.gitea/workflows/build.yaml
+@package.json
+
+
+
+
+
+ Task 1: Add test job to CI pipeline
+ .gitea/workflows/build.yaml
+
+Update .gitea/workflows/build.yaml to add a test job that runs BEFORE build:
+
+1. Add new `test` job at the beginning of jobs section:
+```yaml
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run type check
+ run: npm run check -- --output machine
+
+ - name: Install Playwright browsers
+ run: npx playwright install --with-deps chromium
+
+ - name: Run unit tests with coverage
+ run: npm run test:coverage
+
+ - name: Run E2E tests
+ run: npm run test:e2e
+ env:
+ CI: true
+
+ - name: Upload test artifacts
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: test-results
+ path: |
+ coverage/
+ playwright-report/
+ test-results/
+ retention-days: 7
+```
+
+2. Modify existing `build` job to depend on test:
+```yaml
+ build:
+ needs: test
+ runs-on: ubuntu-latest
+ # ... existing steps ...
+```
+
+This ensures build only runs if tests pass (fail-fast behavior).
+
+
+YAML syntax is valid: `python3 -c "import yaml; yaml.safe_load(open('.gitea/workflows/build.yaml'))"`
+Build job has `needs: test` dependency
+
+ Test job added to pipeline. Build job depends on test job (fail-fast).
+
+
+
+ Task 2: Add Slack notification on failure
+ .gitea/workflows/build.yaml
+
+Add a notify job that runs on failure:
+
+```yaml
+ notify:
+ needs: [test, build]
+ runs-on: ubuntu-latest
+ if: failure()
+ steps:
+ - name: Notify Slack on failure
+ env:
+ SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
+ run: |
+ curl -X POST -H 'Content-type: application/json' \
+ --data "{\"text\":\"Pipeline failed for ${{ gitea.repository }} on ${{ gitea.ref }}\"}" \
+ $SLACK_WEBHOOK_URL
+```
+
+Note: Using direct curl to Slack webhook rather than a GitHub Action for maximum Gitea compatibility (per RESEARCH.md recommendation).
+
+The SLACK_WEBHOOK_URL secret must be configured in Gitea repository settings by the user (documented in user_setup frontmatter).
+
+
+YAML syntax is valid
+Notify job has `if: failure()` condition
+Notify job depends on both test and build
+
+ Slack notification configured for pipeline failures.
+
+
+
+ Complete CI pipeline with test job, fail-fast behavior, artifact upload, and Slack notification
+
+1. Review the updated .gitea/workflows/build.yaml file structure
+2. Verify the job dependency chain: test -> build -> (notify on failure)
+3. Confirm test job includes all required steps:
+ - Type checking (svelte-check)
+ - Unit tests with coverage (vitest)
+ - E2E tests (playwright)
+4. If ready to test in CI:
+ - Push a commit to trigger the pipeline
+ - Monitor Gitea Actions for the test job execution
+ - Verify build job waits for test job to complete
+5. (Optional) Set up SLACK_WEBHOOK_URL secret in Gitea to test failure notifications
+
+ Type "approved" to confirm CI pipeline is correctly configured, or describe any issues found
+
+
+
+
+
+1. .gitea/workflows/build.yaml has test job with:
+ - Type checking step
+ - Unit test with coverage step
+ - E2E test step
+ - Artifact upload step
+2. Build job has `needs: test` (fail-fast)
+3. Notify job runs on failure with Slack webhook
+4. YAML is valid syntax
+5. Pipeline can be triggered on push/PR
+
+
+
+- CI-02 satisfied: Unit tests run in pipeline before build
+- CI-03 satisfied: Type checking runs in pipeline
+- CI-04 satisfied: E2E tests run in pipeline
+- CI-05 satisfied: Pipeline fails fast on test/type errors (needs: test)
+- Slack notification on failure (per CONTEXT.md decision)
+- Test artifacts uploaded for debugging failed runs
+
+
+