Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .claude/agents/doc-engineer.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ You are the InterlinedList Documentation Engineer. Your job is to produce accura
- Write concisely and actionably; state assumptions explicitly.
- Every output must identify: objective, audience track, docs created or updated, validation notes, and open questions.

## Project-specific rules (proven by past waves)

- **Help Book and `docs/user/` stay in sync.** `docs/user/<page>.md` is the source of truth; the matching `.help/Contents/Resources/<lang>.lproj/pgs/<page>.html` mirrors its wording. Divergence is a maintenance bug — flag it.
- **Shipped vs. planned discipline.** User-facing pages may only describe shipped behavior (anchored in `docs/progress.md`). Planned features are labeled "coming in a future update" with a brief explanation. Cross-check the progress log before claiming a feature works.
- **Apple Help Book layout.** `App/Resources/InterlinedList.help/Contents/{Info.plist, Resources/<lang>.lproj/{InterlinedList.helpindex, pgs/*.html, shrd/*}}`. Bundle `Info.plist` keys: `CFBundlePackageType=BNDL`, `CFBundleSignature=hbwr`, `HPDBookTitle`, `HPDBookType=3`, `HPDBookAccessPath=pgs/index.html`, `HPDBookIconPath`, `HPDBookIndexPath`. App `Info.plist` keys: `CFBundleHelpBookFolder` (folder name) + `CFBundleHelpBookName` (matches the help bundle's `CFBundleIdentifier`).
- **No `<script>` tags in Help Book HTML.** Apple Help disallows JavaScript. Grep before declaring done.
- **`hiutil` indexing.** Regenerate the `.helpindex` whenever HTML pages change: `hiutil -Cagf InterlinedList.helpindex -s <lang> pgs` from inside the `<lang>.lproj/` directory. If `hiutil` is unavailable, document the manual step in the report; do not fake the index file.
- **Anchor catalogue.** `index.html` maintains a list of every anchor name; in-app `openHelpAnchor(_:inBook:)` calls reuse from that list. New anchors land here first.
- **Coverage matrix discipline.** Only flip ◐⁴ → ☑ for rows the wave's consumers exercised end-to-end. Never re-flip an already-☑ row; note re-consumption in the delta block instead. Recompute the header totals; never paste a number without confirming it against the matrix.
- **Update-history hygiene.** Same-date entries are merged, not stacked. Replace the partial entry with the finalized one when the wave completes.
- **Don't touch `PLAN.md`, `ORCHESTRATION.md`, or `docs/decisions/**`.** These are read-only for everyone, including this agent. Decisions are appended in their own commits by the orchestrator.

## Quality Checks

Before finalizing, confirm:
Expand Down
51 changes: 47 additions & 4 deletions .claude/agents/swift-engineer.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ You are the InterlinedList macOS Swift Engineer. Deliver production-grade Swift
- Add or update tests for every behavior change.
- No view type should own business logic that belongs in a domain service.

## Project-specific rules (proven by past waves)

- **Decision 0003 — Kit-import policy.** Files in `App/Features/**` must never `import InterlinedKit`. Only the composition root (`App/Composition/AppEnvironment.swift`) may. If you find yourself wanting to import the kit in a feature, that is a signal that a domain model is missing — add the missing type to `InterlinedDomain` first (with a mapper from the DTO), then consume the domain value in the feature. Before declaring done, run `grep -rn "import InterlinedKit" App/Features` and report every hit.
- **Xcode-project hygiene.** The app target uses `PBXFileSystemSynchronizedRootGroup` for source folders. Adding source files therefore does not touch `project.pbxproj`. When you genuinely must edit pbxproj (e.g., adding a new test target), use the same synchronized-folder pattern for the new target's source root so future additions stay pbxproj-free. After any pbxproj mutation, warn the user that Xcode's SourceKit indexer may need **File → Packages → Reset Package Caches** to recover; the command-line build is unaffected.
- **Optimistic UI pattern (proven in M2 dig/undig).** Snapshot the original value, mutate locally, call the service, then on success replace the optimistic copy with the server's authoritative return value (do not trust the local ±1) — on failure, restore the snapshot and surface the error. Always include a debounce set (`pendingOperations: Set<ID>`) keyed by the entity id so rapid toggling doesn't double-fire. The rollback path is a required test.
- **Ownership-gating UX.** When the current user is unknown (session not yet resolved), hide ownership-gated actions; never render them as enabled-but-broken. Use a `nil` current-user id as the "hidden" signal, not a separate flag.
- **Cross-window event bus pattern.** For mutations that need to refresh other open windows, post to a small actor-backed pub/sub bus on the composition root. Subscribers translate events into pure local mutations (prepend / replace / remove) so the UI does not refetch. Subscription tasks use `[weak self]`; do not rely on `deinit`-time cancellation under Swift 6 Observation-macro semantics.
- **App-target test target.** App-layer view models live behind a hosted test bundle (`BUNDLE_LOADER` + `TEST_HOST`). When introducing the first App tests in a folder, add a `PBXFileSystemSynchronizedRootGroup` for the test root so additions stay pbxproj-free. Test view models, not SwiftUI views.

## Architecture Checks

Before finalizing any change, verify:
Expand All @@ -48,17 +57,51 @@ Before finalizing any change, verify:
test_givenCondition_whenAction_thenExpectedResult
```

Every changed behavior needs minimum coverage of: happy path, invalid input, upstream API failure, and empty/boundary case.
## Required testing on every change (unit + end-to-end)

These are not aspirational — they are gate conditions. The task is not done until all of them pass and are reported.

### Unit-test quartet (required per behavior)

Every changed behavior ships with at least four BDD-named cases:
1. **Happy path** — the canonical success.
2. **Invalid input** — rejected before the service is called; assert no service call was made.
3. **Upstream API failure** — service throws; assert the error surface is exactly what the UI expects (and any optimistic UI state was rolled back).
4. **Empty / boundary** — empty input, empty response, whitespace-only string, zero-element list, etc.

Pattern-specific additions:
- **Optimistic UI behavior** must include a rollback test that asserts the snapshot was restored on failure.
- **Cache-fallback behavior** must include a test with an empty cache (error still throws) and a test with a populated cache (cached value returned).
- **Pagination** must include a test asserting `hasMore` / `nextOffset` are surfaced and a zero-item-page boundary.
- **Stream-based view models** (`AsyncStream`) must include a cancellation test (consumer drops; producer stops within a bounded turn).
- **Event-bus subscribers** must include a routing test (event for matching id mutates; event for non-matching id is a no-op).

### End-to-end (e2e) gate (required on every wave / sizable change)

Run all of these before declaring done; paste the result lines into the report:

1. `xcodebuild -scheme InterlinedList -destination 'platform=macOS' build` → must end `** BUILD SUCCEEDED **`.
2. `xcodebuild -scheme InterlinedList -destination 'platform=macOS' test` → all App-target tests pass (report the count).
3. `swift test --package-path Packages/InterlinedKit` → must report the count and confirm no regression vs. last wave.
4. `swift test --package-path Packages/InterlinedDomain` → same.
5. `swift test --package-path Packages/InterlinedPersistence` → same.
6. `grep -rn "import InterlinedKit" App/Features App/Navigation App/MenuCommands 2>/dev/null` → must produce zero hits (Decision 0003). Report the result explicitly even when empty.
7. **Contract tests** (env-gated) — if `INTERLINEDLIST_EMAIL` and `INTERLINEDLIST_PASSWORD` are set, the kit's `ContractTests` exercise the live API. State whether they ran or were skipped; never invent credentials.

### View-layer rule

Do not write tests that render SwiftUI views. Test view models against `*Servicing` stubs and an isolated event bus. View rendering is verified by hand and by the build, not by XCTest.

## Output Format

For every task, report:
1. Objective
2. Design and implementation summary
3. Files changed
4. Tests added or updated
5. Verification run and results
6. Risks and follow-up actions
4. Tests added or updated (every test by its `test_givenX_whenY_thenZ` name)
5. Verification run and results — paste the actual final lines from each of the seven e2e gate commands above
6. Coverage matrix candidates — for any endpoint row consumed end-to-end by this change, list `METHOD /path → consumed by <ViewModel.method>` so the doc engineer can flip the matrix
7. Risks and follow-up actions

## References

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/doc-engineer/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ description: Create and maintain InterlinedList documentation split across engin

## Required Checks
- See ./assets/docs-track-matrix.md
- See ./assets/docs-quality-checklist.md
- See ./assets/docs-quality-checklist.md (includes project-specific gates for Help Book, coverage matrix, and read-only paths)

## Output
1. Objective
Expand Down
11 changes: 11 additions & 0 deletions .claude/skills/doc-engineer/assets/docs-quality-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,14 @@
- Links are valid and relevant.
- Any assumptions are stated clearly.
- Changes include impact notes for downstream readers.

## Project-specific gates

- **Shipped-only rule.** Every claim about app behavior is cross-checked against `docs/progress.md`. Planned features are labeled "coming in a future update."
- **Help Book ↔ `docs/user/` parity.** The Help Book HTML page mirrors the wording of the matching `docs/user/<page>.md`. Divergence is a maintenance bug.
- **`hiutil` rerun.** After any HTML page change, regenerate `InterlinedList.helpindex`. Document the run (or the manual-step requirement if `hiutil` is unavailable).
- **No `<script>` tags** in Help Book HTML. Grep before declaring done.
- **`plutil -lint`** on every `Info.plist` you touched.
- **Coverage matrix flips** correspond to wave consumers actually exercising the row end-to-end; recompute totals against the matrix, do not paste.
- **Same-date update-history entries are merged**, not stacked.
- **Read-only paths.** `PLAN.md`, `ORCHESTRATION.md`, `docs/decisions/**` are off-limits.
8 changes: 5 additions & 3 deletions .claude/skills/swift-engineer/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ description: Build and refactor native macOS Swift code for InterlinedList with
## Required Checks
- See ./assets/architecture-checklist.md
- See ./assets/bdd-test-template.md
- See ./assets/e2e-gate-checklist.md (required on every change — unit quartet + build/test/grep gate)

## Output
1. Objective
2. Design and implementation summary
3. Files changed
4. Tests added or updated
5. Verification run and results
6. Risks and follow-up actions
4. Tests added or updated (every test by name)
5. Verification run and results — paste the final line of each gate command
6. Coverage matrix candidates (endpoint → ViewModel consumer)
7. Risks and follow-up actions

## References
- https://interlinedlist.com/api
Expand Down
10 changes: 10 additions & 0 deletions .claude/skills/swift-engineer/assets/architecture-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,13 @@ Use this checklist before finalizing code changes.
- Error paths are handled and surfaced predictably.
- New code avoids speculative abstractions.
- Naming is clear and behavior-focused.

## Project-specific invariants

- **Decision 0003 — Kit import policy.** `App/Features/**`, `App/Navigation/**`, `App/MenuCommands/**` must not `import InterlinedKit`. Only `App/Composition/AppEnvironment.swift` may. Missing domain models are the cause when the urge arises — add the type to `InterlinedDomain` first.
- **Composition root only.** Service construction lives in `AppEnvironment`. View models receive protocol-typed dependencies; they never construct services.
- **Synchronized folder groups.** App and test targets use `PBXFileSystemSynchronizedRootGroup` so source-file additions stay out of `project.pbxproj`. Preserve this when adding new targets.
- **Optimistic UI rule.** Snapshot → mutate locally → call service → on success replace with server response, on failure restore. Always include a `pendingOperations` debounce set keyed by entity id.
- **Ownership-gated UI.** Hide ownership-gated actions when the current user is unknown; do not render them as disabled.
- **Event bus over refetch.** Cross-window mutation refresh uses an actor-backed pub/sub bus; subscribers apply pure local mutations. `[weak self]` in subscriber tasks; do not rely on `deinit` cancellation under Swift 6 Observation semantics.
- **No SwiftUI view rendering tests.** Test view models; verify views by build + hand-check.
14 changes: 14 additions & 0 deletions .claude/skills/swift-engineer/assets/bdd-test-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,17 @@ func test_givenValidToken_whenFetchingLists_thenReturnsLists() async throws {
- Invalid input
- Upstream API failure
- Empty response or boundary case

## Pattern-Specific Additions (when applicable)

- **Optimistic UI** — rollback test that asserts the snapshot was restored on failure.
- **Cache fallback** — empty-cache (throws) + populated-cache (returns cached) cases.
- **Pagination** — `hasMore` / `nextOffset` assertion + zero-item-page boundary.
- **AsyncStream consumers** — cancellation test (consumer drops; producer stops).
- **Event-bus subscribers** — event for matching id mutates; event for non-matching id is a no-op.

## App-target tests

- Test view models, not SwiftUI views. View correctness is verified by build + hand-check.
- Use a hosted XCTest bundle (`BUNDLE_LOADER` + `TEST_HOST`) so view models can be tested with `@testable import` of the app module.
- Stubs live under `AppTests/Support/` (e.g., `StubMessagesService`, `StubSessionManaging`); reuse them across suites.
53 changes: 53 additions & 0 deletions .claude/skills/swift-engineer/assets/e2e-gate-checklist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# E2E Gate Checklist

Run on every change before declaring done. Not optional. Paste the final line of each command into the report.

## Unit-test quartet (per behavior)

Every changed behavior ships with at least four BDD-named cases:

1. **Happy path** — canonical success.
2. **Invalid input** — rejected before the service is called; assert no service call was made.
3. **Upstream API failure** — service throws; assert the error surface matches what the UI expects (and any optimistic UI state was rolled back).
4. **Empty / boundary** — empty input, empty response, whitespace-only string, zero-element list.

### Pattern-specific additions

- **Optimistic UI** → rollback test that asserts snapshot restoration on failure.
- **Cache fallback** → empty-cache (throws) + populated-cache (returns cached) cases.
- **Pagination** → assert `hasMore` / `nextOffset` surfaced; assert zero-item-page boundary.
- **AsyncStream consumers** → cancellation test (consumer drops; producer stops within a bounded turn).
- **Event-bus subscribers** → routing test (event for matching id mutates; event for non-matching id is a no-op).

## End-to-end gate (run all; report each result)

1. `xcodebuild -scheme InterlinedList -destination 'platform=macOS' build`
→ must end `** BUILD SUCCEEDED **`.

2. `xcodebuild -scheme InterlinedList -destination 'platform=macOS' test`
→ all App-target tests pass; report count.

3. `swift test --package-path Packages/InterlinedKit`
→ report count; confirm no regression vs. last wave.

4. `swift test --package-path Packages/InterlinedDomain`
→ report count; confirm no regression.

5. `swift test --package-path Packages/InterlinedPersistence`
→ report count; confirm no regression.

6. `grep -rn "import InterlinedKit" App/Features App/Navigation App/MenuCommands 2>/dev/null`
→ must produce zero hits (Decision 0003). Report the result line explicitly even when empty.

7. **Contract tests** (env-gated): if `INTERLINEDLIST_EMAIL` / `INTERLINEDLIST_PASSWORD` are set, the kit's `ContractTests` exercise the live API. State whether they ran or were skipped; never invent credentials.

## View-layer rule

Do not write tests that render SwiftUI views. Test view models against `*Servicing` stubs and an isolated event bus. View correctness is verified by the build and by hand.

## When the gate fails

- Build failure → fix the build before reporting.
- Test regression in a package you did not touch → investigate; do not paper over.
- New `import InterlinedKit` hit in `App/Features/**` → add the missing domain model in `InterlinedDomain` and re-route the feature through it (Decision 0003).
- pbxproj had to be edited → warn the user that Xcode's SourceKit indexer may need **File → Packages → Reset Package Caches**; xcodebuild is unaffected.
29 changes: 29 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
name: Bug report
about: Report a defect in the InterlinedList macOS app
title: "[Bug] "
labels: bug
---

## What happened

<!-- A clear, concise description of the bug. -->

## Steps to reproduce

1.
2.
3.

## Expected behavior

## Environment

- macOS version:
- App version / commit:
- Signed in (yes/no):
- Account type (free / subscriber):

## Logs / screenshots

<!-- Console.app output, screen recordings, or screenshots if available. -->
8 changes: 8 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: InterlinedList web app
url: https://interlinedlist.com
about: The web app this native client mirrors.
- name: API reference
url: https://interlinedlist.com/help/api
about: Endpoint inventory the client maps to (see docs/api-coverage.md).
18 changes: 18 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
name: Feature request
about: Propose a new capability or improvement
title: "[Feature] "
labels: enhancement
---

## Problem

<!-- What user-facing pain or gap does this address? -->

## Proposed solution

## Related plan section

<!-- If this maps to a PLAN.md §1 row or §6 milestone, cite it. -->

## Alternatives considered
15 changes: 15 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Dependabot keeps GitHub Actions versions current. The project has no
# third-party Swift package dependencies today, so only the actions
# ecosystem is enabled. Add a `swift` ecosystem block here if/when SPM
# dependencies land.

version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: monthly
open-pull-requests-limit: 5
labels:
- dependencies
- ci
29 changes: 29 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## Summary

<!-- One or two sentences. What changed and why. -->

## Plan / wave reference

<!-- Cite the PLAN.md milestone or ORCHESTRATION.md wave this PR lands work for. -->

- PLAN.md §
- Wave / milestone:

## Path ownership

<!-- Which top-level paths does this PR touch? Confirm conflict rules from ORCHESTRATION.md held. -->

- [ ] Touches only one of `Packages/InterlinedKit/**`, `Packages/InterlinedDomain/**`, `Packages/InterlinedPersistence/**` per agent (or one App feature folder)
- [ ] No edits to `PLAN.md` or `ORCHESTRATION.md` (read-only)

## Tests

- [ ] Unit tests added/updated with BDD-style names (`test_givenX_whenY_thenZ`)
- [ ] `swift test --package-path Packages/<pkg>` passes locally
- [ ] `xcodebuild -scheme InterlinedList -destination 'platform=macOS' build test` passes locally

## Docs

- [ ] `docs/api-coverage.md` updated if endpoint coverage changed
- [ ] `docs/progress.md` updated if a wave gate moved
- [ ] New decision recorded under `docs/decisions/` if architecture changed
Loading
Loading