Repository navigation
feat(tickets): data model + GitHub/Notion providers - #172
Conversation
Implement the data-side half of first-party ticket-system support. The
shared contract in `src/shared/tickets.ts` already landed; this PR fills
in the slices, persistence, providers, and IPC surface the UI workstream
consumes.
- `ticketProviders` slice: configured provider instances keyed by uuid,
events for loaded / added / updated / removed.
- `tickets` slice: per-provider cache of last `list()` result
(tickets[], loading, lastFetched, error). Dumb timestamp-based
invalidation; events for fetchStarted / fetchSucceeded / fetchFailed.
- `RepoConfig.ticketProviderIds[]` for the M2M repo↔provider link,
persisted in `.harness.json`, with `effectiveTicketProviderIds`
helper.
- `Worktree.linkedTicket` + `PendingWorktree.linkedTicket` carry the
(providerId, externalId) tuple. The side-table for the worktree map
lives in `config.json` under `worktreeTicketLinks`; WorktreesFSM
merges it into the dispatched list on every refresh and prunes
entries on worktree removal.
- `src/main/tickets/{provider-registry,github-issues,notion}.ts`
implement the runtime providers. Notion tokens resolve from
`secrets.enc` keyed `ticket-provider-token:<id>`. GitHub uses the
existing token resolution.
- IPC surface: `tickets:{listProviders,addProvider,updateProvider,
removeProvider,list,get,linkRepoProvider,unlinkRepoProvider}`, plus
`linkedTicket?` threaded through `worktrees:runPending(PR)`.
- One reducer test per event variant in both new slices.
Sibling PR (UI workstream) builds the picker + Settings page against
this surface. Both merge into `feat/tickets`, not `main`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ReviewData-half of the ticketing work, targeting ✅ What's solid
🐛 Bugs to fix before merge1. Notion search filter is dead unless if (query && config.titleProperty !== undefined) {
body.filter = { property: config.titleProperty || 'Name', ... }
}The default for if (query) {
body.filter = { property: config.titleProperty || 'Name', title: { contains: query } }
}2. 3. 4. Stored token for a
|
Addresses Claude's review on #172: - Notion search filter now triggers on `query` alone (was gated on `config.titleProperty !== undefined`, which made search dead for every provider that didn't explicitly set the property name). - `validateProviderConfig(type, raw)` in provider-registry shape-checks the discriminated `config` payload before it lands on disk. Both `tickets:addProvider` and `tickets:updateProvider` validate; type changes via update are refused (use remove + add instead). - Token slot is now only used for Notion providers — GitHub reuses the shared PAT / gh-cli token, so any token passed for a `github-issues` add is dropped silently. - `tickets:removeProvider` now also strips `worktreeTicketLinks` entries pointing at the removed provider, and triggers a `refreshList` so the linkedTicket disappears from the sidebar decoration in one render. - `WorktreesFSM.refreshList` calls a new `pruneWorktreeTicketLinks` callback with the live worktree path set, so side-table orphans (left behind by setup-failure cleanup, manual `git worktree remove`, or an older Harness) get garbage-collected. - `tickets:listProviders` reads from `store.getSnapshot()` instead of `config` directly — the store is canonical. - `worktree:remove` IPC handler routes through the named `setWorktreeTicketLink` helper instead of poking `config` directly. - `trackedFetch` aliased to `recordedFetch` in `notion.ts` — the name is GitHub-flavored but the function is generic, and Notion latency showing up under "github-recorder" in stack traces was misleading. Tests added: - `WorktreesFSM.refreshList` decorates from the side-table, calls the prune callback with the right path set, and returns the git list verbatim when no getter is wired. - `validateProviderConfig` for both provider types, including cross-type-shape rejection and unknown-field stripping. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Fixed in e09da15: Bugs
Smaller concerns
Tests
714 tests pass on the branch. Comment left on behalf of @frenchie4111 by Claude via Harness. |
Conflicts in build-backend.ts + types.ts — resolved by keeping the nested `tickets: TicketsAPI` namespace the UI consumes (backed by the in-renderer stub) and dropping the flat `ticketsListProviders` / `ticketsLinkRepoProvider` / etc. methods the data PR added. The data slice models per-repo M2M (`linkRepoProvider` / `unlinkRepoProvider` + `RepoConfig.ticketProviderIds`); the UI here drives the inverted "provider owns its project membership" model (`appliesToRepoRoots`). Reconciling — extending the data slice with `appliesToRepoRoots` and deleting the M2M side-table — is the follow-up tracked in the build-backend.ts comment block; once that lands, the stub goes away. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Data-side half of the first-party ticketing-system work. Sibling PR (UI workstream) builds the picker + Settings page against this surface. Both PRs target
feat/tickets, notmain.The shared contract in
src/shared/tickets.tsis already onfeat/tickets; this PR fills in the slices, persistence, providers, and IPC surface the UI consumes.What's in here
ticketProvidersslice — configured provider instances keyed by uuid. Events:loaded,added,updated,removed.ticketsslice — per-provider cache of lastlist()result (tickets[],loading,lastFetched,error). Dumb timestamp-based invalidation; events:fetchStarted,fetchSucceeded,fetchFailed.RepoConfig.ticketProviderIds[]persisted in.harness.json, witheffectiveTicketProviderIds()helper.Worktree.linkedTicket+PendingWorktree.linkedTicketcarry the(providerId, externalId)tuple. Side-table for the worktree map lives inconfig.jsonunderworktreeTicketLinks;WorktreesFSMmerges it into the dispatched list on every refresh and prunes entries on worktree removal.src/main/tickets/{provider-registry,github-issues,notion}.ts. GitHub uses the existing token resolution + filters PRs out of the issues endpoint. Notion tokens resolve fromsecrets.enckeyedticket-provider-token:<id>.tickets:{listProviders, addProvider, updateProvider, removeProvider, list, get, linkRepoProvider, unlinkRepoProvider}pluslinkedTicket?threaded throughworktrees:runPending(PR).effectiveTicketProviderIds.Out of scope (UI workstream's PR)
addWorktreeacceptslinkedTicket).Test plan
npm run typechecknpx electron-vite buildnpx vitest run src/shared(696 passed)config.jsonmanually, hittickets:listvia devtools, confirm tickets show uptickets:list, confirm a real database returnslinkedTicketset, restart Harness, confirm the link survives the boot🤖 Generated with Claude Code