diff --git a/fixtures/github/issues.pull_request.PR_kwDOAgent47.json b/fixtures/github/issues.pull_request.PR_kwDOAgent47.json index c35e1d4..a44f63c 100644 --- a/fixtures/github/issues.pull_request.PR_kwDOAgent47.json +++ b/fixtures/github/issues.pull_request.PR_kwDOAgent47.json @@ -14,5 +14,5 @@ "head_branch_name": "feat/worker-context-envelope", "head_repo_slug": "kennethnym/agent-tooling", "head_ref": "4a8df12be732c0f9e5d194cd2af7430c0d2fb8d4", - "body": "## Goal\n\nSplit context loading from execution workers so delegation stays predictable while this pull request is still in draft.\n\n### Why\n- workers should receive a compact payload\n- prompt packing should be testable without spawning a worker\n- retry policy should stay in one place\n\n### Boundaries\n| Boundary | Responsibility |\n| --- | --- |\n| `ContextLoader` | Hydrate repository and file context |\n| `PromptAssembler` | Build compact worker payloads |\n| `WorkerRunner` | Apply retry policy and collect results |\n\n### Proposed flow\n1. Load repository context once.\n2. Normalize file excerpts and metadata.\n3. Hand workers a stable execution envelope.\n\n```text\nContextLoader -> PromptAssembler -> WorkerRunner\n```\n\n> Draft status stays until we decide whether token counts belong in the worker response.\n\n### Questions\n- Should `ContextLoader` expose cache hit metrics?\n- Should worker retries carry the same prompt hash?\n- [ ] Add a regression test for interrupted workers" + "body": "## Goal\n\nSplit context loading from execution workers so delegation stays predictable while this pull request is still in draft.\n\n### Why\n- workers should receive a compact payload\n- prompt packing should be testable without spawning a worker\n- retry policy should stay in one place\n\n### Boundaries\n| Boundary | Responsibility |\n| --- | --- |\n| `ContextLoader` | Hydrate repository and file context |\n| `PromptAssembler` | Build compact worker payloads |\n| `WorkerRunner` | Apply retry policy and collect results |\n\n### Proposed flow\n1. Load repository context once.\n2. Normalize file excerpts and metadata.\n3. Hand workers a stable execution envelope.\n\n```text\nContextLoader -> PromptAssembler -> WorkerRunner\n```\n\n> Draft status stays until we decide whether token counts belong in the worker response.\n\n### Questions\n- Should `ContextLoader` expose cache hit metrics?\n- Should worker retries carry the same prompt hash?\n- Add a regression test for interrupted workers" } diff --git a/fixtures/github/issues.pull_request.PR_kwDODesign31.json b/fixtures/github/issues.pull_request.PR_kwDODesign31.json index 3ee4e03..b0acede 100644 --- a/fixtures/github/issues.pull_request.PR_kwDODesign31.json +++ b/fixtures/github/issues.pull_request.PR_kwDODesign31.json @@ -14,5 +14,5 @@ "head_branch_name": "chore/dashboard-spacing-scale", "head_repo_slug": "kennethnym/design-notes", "head_ref": "5b0cf338ec46d581af0d582da6427a3dfbce9018", - "body": "## Summary\n\nTightens the dashboard spacing scale before the next visual refresh.\n\n### Updated tokens\n- `space.3` for compact sidebar gaps\n- `space.5` for section rhythm\n- `space.8` for page-level separation\n\n| Surface | Before | After |\n| --- | --- | --- |\n| Sidebar section gap | `space.6` | `space.5` |\n| Filter row padding | `space.4` | `space.3` |\n| Dashboard gutter | `space.7` | `space.6` |\n\n### Review notes\n- verify heading baselines still align with list content\n- compare 1280px and 1440px screenshots side by side\n- [ ] revisit compact mode once the nav collapse lands\n\n**Design intent:** make dense screens feel more deliberate without looking cramped." + "body": "## Summary\n\nTightens the dashboard spacing scale before the next visual refresh.\n\n### Updated tokens\n- `space.3` for compact sidebar gaps\n- `space.5` for section rhythm\n- `space.8` for page-level separation\n\n| Surface | Before | After |\n| --- | --- | --- |\n| Sidebar section gap | `space.6` | `space.5` |\n| Filter row padding | `space.4` | `space.3` |\n| Dashboard gutter | `space.7` | `space.6` |\n\n### Review notes\n- verify heading baselines still align with list content\n- compare 1280px and 1440px screenshots side by side\n- revisit compact mode once the nav collapse lands\n\n**Design intent:** make dense screens feel more deliberate without looking cramped." } diff --git a/fixtures/github/issues.pull_request.PR_kwDOInfra19.json b/fixtures/github/issues.pull_request.PR_kwDOInfra19.json index 408e84f..58a3329 100644 --- a/fixtures/github/issues.pull_request.PR_kwDOInfra19.json +++ b/fixtures/github/issues.pull_request.PR_kwDOInfra19.json @@ -14,5 +14,5 @@ "head_branch_name": "docs/manual-failover-steps", "head_repo_slug": "kennethnym/infra-scripts", "head_ref": "6fd11baf0d9d53d18f6d7b7dc265d9b09e6f4217", - "body": "## Context\n\nDocuments the manual failover sequence for the staging stack while the automated recovery path is still unstable.\n\n### Draft runbook\n1. Put the primary deployment in maintenance mode.\n2. Promote the standby database.\n3. Repoint the app workers.\n4. Warm the cache before reopening traffic.\n\n| Step | Owner | State |\n| --- | --- | --- |\n| Promote standby | SRE | Drafted |\n| Repoint workers | App platform | Drafted |\n| DNS validation | Release lead | Pending |\n\n```bash\n./scripts/failover promote-standby --env staging\n./scripts/failover repoint-workers --env staging\n./scripts/failover verify --env staging\n```\n\n> This pull request was closed because the final DNS validation steps were still changing underneath the runbook.\n\n### Remaining gaps\n- secrets rotation is still manual\n- rollback screenshots are missing\n- [ ] add the final post-cutover checklist" + "body": "## Context\n\nDocuments the manual failover sequence for the staging stack while the automated recovery path is still unstable.\n\n### Draft runbook\n1. Put the primary deployment in maintenance mode.\n2. Promote the standby database.\n3. Repoint the app workers.\n4. Warm the cache before reopening traffic.\n\n| Step | Owner | State |\n| --- | --- | --- |\n| Promote standby | SRE | Drafted |\n| Repoint workers | App platform | Drafted |\n| DNS validation | Release lead | Pending |\n\n```bash\n./scripts/failover promote-standby --env staging\n./scripts/failover repoint-workers --env staging\n./scripts/failover verify --env staging\n```\n\n> This pull request was closed because the final DNS validation steps were still changing underneath the runbook.\n\n### Remaining gaps\n- secrets rotation is still manual\n- rollback screenshots are missing\n- add the final post-cutover checklist" } diff --git a/fixtures/github/issues.pull_request.PR_kwDONovem84.json b/fixtures/github/issues.pull_request.PR_kwDONovem84.json index 8f048cd..e71d84b 100644 --- a/fixtures/github/issues.pull_request.PR_kwDONovem84.json +++ b/fixtures/github/issues.pull_request.PR_kwDONovem84.json @@ -14,5 +14,5 @@ "head_branch_name": "feat/cached-issue-pane", "head_repo_slug": "kennethnym/novem", "head_ref": "2bc41de7731b9ef48f7d64ee9f0d5f497dbe0a51", - "body": "## Summary\n\nHydrates the dashboard issue pane from cached query state so selection and scroll position stay stable during refetches.\n\n### Rendering coverage\n- [x] headings\n- [x] bullet lists\n- [x] task list items\n- [x] inline code like `use_query`\n- [x] tables\n\n### Implementation sketch\n```rust\nlet cached = query_store.read(key);\nlet selection = cached.and_then(|data| data.selected_issue_id.clone());\n```\n\n| Case | Expected behavior |\n| --- | --- |\n| Cache hit | Keep the current selection pinned |\n| Cache miss | Fall back to the first visible item |\n| Refetch in flight | Preserve scroll position |\n\n### Follow-up\n- [ ] mirror the same cache behavior in the pull request detail pane\n- [ ] add a smoke test around keyboard navigation during refetch\n\nSee also the [query store](src/query.rs) integration notes." + "body": "## Summary\n\nHydrates the dashboard issue pane from cached query state so selection and scroll position stay stable during refetches.\n\n### Rendering coverage\n- headings\n- bullet lists\n- inline code like `use_query`\n- tables\n\n### Implementation sketch\n```rust\nlet cached = query_store.read(key);\nlet selection = cached.and_then(|data| data.selected_issue_id.clone());\n```\n\n| Case | Expected behavior |\n| --- | --- |\n| Cache hit | Keep the current selection pinned |\n| Cache miss | Fall back to the first visible item |\n| Refetch in flight | Preserve scroll position |\n\n### Follow-up\n- mirror the same cache behavior in the pull request detail pane\n- add a smoke test around keyboard navigation during refetch\n\nSee also the [query store](src/query.rs) integration notes." } diff --git a/fixtures/github/issues.pull_request.PR_kwDONovem85.json b/fixtures/github/issues.pull_request.PR_kwDONovem85.json index 96c1f38..a59dff3 100644 --- a/fixtures/github/issues.pull_request.PR_kwDONovem85.json +++ b/fixtures/github/issues.pull_request.PR_kwDONovem85.json @@ -14,5 +14,5 @@ "head_branch_name": "feat/cached-repo-picker", "head_repo_slug": "kennethnym/novem", "head_ref": "13af7d0b48a6ce0b22d48c9b6c1c78dfcd94e6a0", - "body": "## Summary\n\nIntroduces a cached repository query so the titlebar picker can switch context without hitting GitHub on every open.\n\n### Why\n- reduces flicker while the picker opens\n- keeps recent repositories visible during short reconnects\n- avoids duplicate requests when the titlebar rerenders\n\n### Cache rules\n- explicit refresh invalidates the cached list\n- fresh network data still wins when available\n- empty responses should not overwrite a warm cache\n\n**Fast path:** render the warm cache immediately.\n*Background refresh* still reconciles stale rows.\n~~Empty refreshes~~ should never clear visible repositories.\n\n| Cache path | Expected behavior |\n| --- | --- |\n| Warm cache | Render repositories before the refresh finishes |\n| Refresh success | Replace cached rows with fresh network data |\n| Empty response | Keep the previous warm cache intact |\n\n```text\nopen picker -> read cache -> render immediately -> refresh in background\n```\n\n### Follow-up\n1. Measure cache hit rate in debug builds.\n2. Add eviction telemetry.\n3. [ ] Consider persisting the last successful repository list across launches." + "body": "## Summary\n\nIntroduces a cached repository query so the titlebar picker can switch context without hitting GitHub on every open.\n\n### Why\n- reduces flicker while the picker opens\n- keeps recent repositories visible during short reconnects\n- avoids duplicate requests when the titlebar rerenders\n\n### Cache rules\n- explicit refresh invalidates the cached list\n- fresh network data still wins when available\n- empty responses should not overwrite a warm cache\n\n**Fast path:** render the warm cache immediately.\n*Background refresh* still reconciles stale rows.\n~~Empty refreshes~~ should never clear visible repositories.\n\n| Cache path | Expected behavior |\n| --- | --- |\n| Warm cache | Render repositories before the refresh finishes |\n| Refresh success | Replace cached rows with fresh network data |\n| Empty response | Keep the previous warm cache intact |\n\n```text\nopen picker -> read cache -> render immediately -> refresh in background\n```\n\n### Follow-up\n1. Measure cache hit rate in debug builds.\n2. Add eviction telemetry.\n3. Consider persisting the last successful repository list across launches." } diff --git a/fixtures/github/issues.pull_request.PR_kwDOSprint62.json b/fixtures/github/issues.pull_request.PR_kwDOSprint62.json index d713ab9..10a37d7 100644 --- a/fixtures/github/issues.pull_request.PR_kwDOSprint62.json +++ b/fixtures/github/issues.pull_request.PR_kwDOSprint62.json @@ -14,5 +14,5 @@ "head_branch_name": "feat/release-handoff-checklist", "head_repo_slug": "kennethnym/sprint-planner", "head_ref": "be7a8114a57f3e9d214cb9af457c10fd6c5a0b21", - "body": "## Release handoff checklist\n\nAdds the release checklist views and closes the loop for the May rollout.\n\n### Included\n- launch readiness checklist for QA, docs, and release engineering\n- handoff status badges in the weekly planner\n- empty-state copy for weeks without a scheduled release\n\n| Stage | Owner | Status |\n| --- | --- | --- |\n| QA sign-off | `@mariahops` | Done |\n| Docs publish | `@rorycraft` | Done |\n| Release window confirm | `@kennethnym` | Done |\n\n### Verification\n1. Open a release week and confirm checklist sections render in order.\n2. Mark each handoff item complete and confirm the summary badge updates.\n3. Review the planner on a narrow viewport.\n\n> The merged version intentionally keeps the checklist readable even when one section has no pending items.\n\n- [x] QA sign-off state is visible\n- [x] Docs handoff state is visible\n- [ ] Add screenshot coverage for the compact layout" + "body": "## Release handoff checklist\n\nAdds the release checklist views and closes the loop for the May rollout.\n\n### Included\n- launch readiness checklist for QA, docs, and release engineering\n- handoff status badges in the weekly planner\n- empty-state copy for weeks without a scheduled release\n\n| Stage | Owner | Status |\n| --- | --- | --- |\n| QA sign-off | `@mariahops` | Done |\n| Docs publish | `@rorycraft` | Done |\n| Release window confirm | `@kennethnym` | Done |\n\n### Verification\n1. Open a release week and confirm checklist sections render in order.\n2. Mark each handoff item complete and confirm the summary badge updates.\n3. Review the planner on a narrow viewport.\n\n> The merged version intentionally keeps the checklist readable even when one section has no pending items.\n\n- QA sign-off state is visible\n- Docs handoff state is visible\n- Add screenshot coverage for the compact layout" } diff --git a/fixtures/github/issues.pull_requests.all.page1.json b/fixtures/github/issues.pull_requests.all.page1.json index 9a668c9..e2b1b4f 100644 --- a/fixtures/github/issues.pull_requests.all.page1.json +++ b/fixtures/github/issues.pull_requests.all.page1.json @@ -12,7 +12,7 @@ "state": "open", "state_reason": null, "title": "feat(dashboard): hydrate issue pane from cached query state", - "body": "## Summary\n\nHydrates the dashboard issue pane from cached query state so selection and scroll position stay stable during refetches.\n\n### Changes\n- reuse the cached query result before the network request resolves\n- keep the selected issue id pinned across list refreshes\n- fall back to the first visible item when the cached selection disappears\n\n### Behavior matrix\n| Case | Expected behavior |\n| --- | --- |\n| Cache hit | Keep the current selection pinned |\n| Cache miss | Fall back to the first visible item |\n| Refetch in flight | Preserve scroll position |\n\n### Follow-up\n- [ ] mirror the same cache behavior in the pull request detail pane\n- [ ] add a smoke test for refetch during keyboard navigation", + "body": "## Summary\n\nHydrates the dashboard issue pane from cached query state so selection and scroll position stay stable during refetches.\n\n### Changes\n- reuse the cached query result before the network request resolves\n- keep the selected issue id pinned across list refreshes\n- fall back to the first visible item when the cached selection disappears\n\n### Behavior matrix\n| Case | Expected behavior |\n| --- | --- |\n| Cache hit | Keep the current selection pinned |\n| Cache miss | Fall back to the first visible item |\n| Refetch in flight | Preserve scroll position |\n\n### Follow-up\n- mirror the same cache behavior in the pull request detail pane\n- add a smoke test for refetch during keyboard navigation", "body_text": "Wires the dashboard issue list to the query store and keeps selection stable while refetching.", "body_html": null, "user": { @@ -230,7 +230,7 @@ "state": "open", "state_reason": null, "title": "feat(repo): add cached repository query for titlebar picker", - "body": "## Summary\n\nIntroduces a cached repository query so the titlebar picker can switch context without hitting GitHub on every open.\n\n### Why\n- reduces flicker while the picker opens\n- keeps recent repositories available during short reconnects\n- avoids duplicate requests when the titlebar rerenders\n\n### Notes\n- cache invalidates on explicit refresh\n- fresh network data still wins when available\n- [ ] follow up with eviction metrics\n\n**Fast path:** render the warm cache immediately.\n*Background refresh* still reconciles stale rows.\n~~Empty refreshes~~ should never clear visible repositories.\n\n### Cache paths\n| Cache path | Expected behavior |\n| --- | --- |\n| Warm cache | Render repositories before the refresh finishes |\n| Refresh success | Replace cached rows with fresh network data |\n| Empty response | Keep the previous warm cache intact |", + "body": "## Summary\n\nIntroduces a cached repository query so the titlebar picker can switch context without hitting GitHub on every open.\n\n### Why\n- reduces flicker while the picker opens\n- keeps recent repositories available during short reconnects\n- avoids duplicate requests when the titlebar rerenders\n\n### Notes\n- cache invalidates on explicit refresh\n- fresh network data still wins when available\n- follow up with eviction metrics\n\n**Fast path:** render the warm cache immediately.\n*Background refresh* still reconciles stale rows.\n~~Empty refreshes~~ should never clear visible repositories.\n\n### Cache paths\n| Cache path | Expected behavior |\n| --- | --- |\n| Warm cache | Render repositories before the refresh finishes |\n| Refresh success | Replace cached rows with fresh network data |\n| Empty response | Keep the previous warm cache intact |", "body_text": "Introduces a repository list query so the titlebar can switch context without hitting GitHub repeatedly.", "body_html": null, "user": { diff --git a/fixtures/github/issues.pull_requests.all.page2.json b/fixtures/github/issues.pull_requests.all.page2.json index fa2097b..a6af890 100644 --- a/fixtures/github/issues.pull_requests.all.page2.json +++ b/fixtures/github/issues.pull_requests.all.page2.json @@ -12,7 +12,7 @@ "state": "open", "state_reason": null, "title": "chore(tokens): tighten dashboard spacing scale", - "body": "## Summary\n\nNormalizes horizontal gutters and sidebar section padding before the visual refresh.\n\n### Token updates\n- `space.3` now anchors compact sidebar gaps\n- `space.5` is used for section-to-section rhythm\n- `space.8` stays reserved for page-level breaks\n\n| Surface | Before | After |\n| --- | --- | --- |\n| Sidebar section gap | `space.6` | `space.5` |\n| Filter row padding | `space.4` | `space.3` |\n| Dashboard gutter | `space.7` | `space.6` |\n\n### Review notes\n- compare the dashboard at 1280px and 1440px\n- verify headings still align with list rows\n- [ ] revisit mobile spacing once nav collapse lands", + "body": "## Summary\n\nNormalizes horizontal gutters and sidebar section padding before the visual refresh.\n\n### Token updates\n- `space.3` now anchors compact sidebar gaps\n- `space.5` is used for section-to-section rhythm\n- `space.8` stays reserved for page-level breaks\n\n| Surface | Before | After |\n| --- | --- | --- |\n| Sidebar section gap | `space.6` | `space.5` |\n| Filter row padding | `space.4` | `space.3` |\n| Dashboard gutter | `space.7` | `space.6` |\n\n### Review notes\n- compare the dashboard at 1280px and 1440px\n- verify headings still align with list rows\n- revisit mobile spacing once nav collapse lands", "body_text": "Normalizes horizontal gutters and sidebar section padding before the visual refresh.", "body_html": null, "user": { @@ -117,7 +117,7 @@ "state": "closed", "state_reason": "not_planned", "title": "docs(deploy): document manual failover steps", - "body": "## Context\n\nDocuments the manual failover sequence for the staging stack while the automated path is still unstable.\n\n### Draft runbook\n1. Put the primary deployment in maintenance mode.\n2. Promote the standby database.\n3. Repoint the app workers and warm the cache.\n4. Verify health checks before reopening traffic.\n\n| Step | Owner | State |\n| --- | --- | --- |\n| Promote standby | SRE | Drafted |\n| Repoint workers | App platform | Drafted |\n| DNS validation | Release lead | Pending |\n\n### Risks\n- secrets rotation is still manual\n- rollback steps need screenshots\n- [ ] add the final DNS validation command", + "body": "## Context\n\nDocuments the manual failover sequence for the staging stack while the automated path is still unstable.\n\n### Draft runbook\n1. Put the primary deployment in maintenance mode.\n2. Promote the standby database.\n3. Repoint the app workers and warm the cache.\n4. Verify health checks before reopening traffic.\n\n| Step | Owner | State |\n| --- | --- | --- |\n| Promote standby | SRE | Drafted |\n| Repoint workers | App platform | Drafted |\n| DNS validation | Release lead | Pending |\n\n### Risks\n- secrets rotation is still manual\n- rollback steps need screenshots\n- add the final DNS validation command", "body_text": null, "body_html": null, "user": { diff --git a/fixtures/github/issues.pull_requests.assigned.page1.json b/fixtures/github/issues.pull_requests.assigned.page1.json index 27ab3c9..80676e8 100644 --- a/fixtures/github/issues.pull_requests.assigned.page1.json +++ b/fixtures/github/issues.pull_requests.assigned.page1.json @@ -246,7 +246,7 @@ "state": "open", "state_reason": null, "title": "chore(tokens): tighten dashboard spacing scale", - "body": "## Summary\n\nNormalizes horizontal gutters and sidebar section padding before the visual refresh.\n\n### Token updates\n- `space.3` now anchors compact sidebar gaps\n- `space.5` is used for section-to-section rhythm\n- `space.8` stays reserved for page-level breaks\n\n| Surface | Before | After |\n| --- | --- | --- |\n| Sidebar section gap | `space.6` | `space.5` |\n| Filter row padding | `space.4` | `space.3` |\n| Dashboard gutter | `space.7` | `space.6` |\n\n### Review notes\n- compare the dashboard at 1280px and 1440px\n- verify headings still align with list rows\n- [ ] revisit mobile spacing once nav collapse lands", + "body": "## Summary\n\nNormalizes horizontal gutters and sidebar section padding before the visual refresh.\n\n### Token updates\n- `space.3` now anchors compact sidebar gaps\n- `space.5` is used for section-to-section rhythm\n- `space.8` stays reserved for page-level breaks\n\n| Surface | Before | After |\n| --- | --- | --- |\n| Sidebar section gap | `space.6` | `space.5` |\n| Filter row padding | `space.4` | `space.3` |\n| Dashboard gutter | `space.7` | `space.6` |\n\n### Review notes\n- compare the dashboard at 1280px and 1440px\n- verify headings still align with list rows\n- revisit mobile spacing once nav collapse lands", "body_text": "Normalizes horizontal gutters and sidebar section padding before the visual refresh.", "body_html": null, "user": { diff --git a/fixtures/github/issues.pull_requests.created.page1.json b/fixtures/github/issues.pull_requests.created.page1.json index 330804e..4f98bdf 100644 --- a/fixtures/github/issues.pull_requests.created.page1.json +++ b/fixtures/github/issues.pull_requests.created.page1.json @@ -12,7 +12,7 @@ "state": "open", "state_reason": null, "title": "feat(repo): add cached repository query for titlebar picker", - "body": "## Summary\n\nIntroduces a cached repository query so the titlebar picker can switch context without hitting GitHub on every open.\n\n### Why\n- reduces flicker while the picker opens\n- keeps recent repositories available during short reconnects\n- avoids duplicate requests when the titlebar rerenders\n\n### Notes\n- cache invalidates on explicit refresh\n- fresh network data still wins when available\n- [ ] follow up with eviction metrics\n\n**Fast path:** render the warm cache immediately.\n*Background refresh* still reconciles stale rows.\n~~Empty refreshes~~ should never clear visible repositories.\n\n### Cache paths\n| Cache path | Expected behavior |\n| --- | --- |\n| Warm cache | Render repositories before the refresh finishes |\n| Refresh success | Replace cached rows with fresh network data |\n| Empty response | Keep the previous warm cache intact |", + "body": "## Summary\n\nIntroduces a cached repository query so the titlebar picker can switch context without hitting GitHub on every open.\n\n### Why\n- reduces flicker while the picker opens\n- keeps recent repositories available during short reconnects\n- avoids duplicate requests when the titlebar rerenders\n\n### Notes\n- cache invalidates on explicit refresh\n- fresh network data still wins when available\n- follow up with eviction metrics\n\n**Fast path:** render the warm cache immediately.\n*Background refresh* still reconciles stale rows.\n~~Empty refreshes~~ should never clear visible repositories.\n\n### Cache paths\n| Cache path | Expected behavior |\n| --- | --- |\n| Warm cache | Render repositories before the refresh finishes |\n| Refresh success | Replace cached rows with fresh network data |\n| Empty response | Keep the previous warm cache intact |", "body_text": "Introduces a repository list query so the titlebar can switch context without hitting GitHub repeatedly.", "body_html": null, "user": { @@ -126,7 +126,7 @@ "state": "open", "state_reason": null, "title": "feat(dashboard): hydrate issue pane from cached query state", - "body": "## Summary\n\nHydrates the dashboard issue pane from cached query state so selection and scroll position stay stable during refetches.\n\n### Changes\n- reuse the cached query result before the network request resolves\n- keep the selected issue id pinned across list refreshes\n- fall back to the first visible item when the cached selection disappears\n\n### Behavior matrix\n| Case | Expected behavior |\n| --- | --- |\n| Cache hit | Keep the current selection pinned |\n| Cache miss | Fall back to the first visible item |\n| Refetch in flight | Preserve scroll position |\n\n### Follow-up\n- [ ] mirror the same cache behavior in the pull request detail pane\n- [ ] add a smoke test for refetch during keyboard navigation", + "body": "## Summary\n\nHydrates the dashboard issue pane from cached query state so selection and scroll position stay stable during refetches.\n\n### Changes\n- reuse the cached query result before the network request resolves\n- keep the selected issue id pinned across list refreshes\n- fall back to the first visible item when the cached selection disappears\n\n### Behavior matrix\n| Case | Expected behavior |\n| --- | --- |\n| Cache hit | Keep the current selection pinned |\n| Cache miss | Fall back to the first visible item |\n| Refetch in flight | Preserve scroll position |\n\n### Follow-up\n- mirror the same cache behavior in the pull request detail pane\n- add a smoke test for refetch during keyboard navigation", "body_text": "Wires the dashboard issue list to the query store and keeps selection stable while refetching.", "body_html": null, "user": { diff --git a/fixtures/github/issues.pull_requests.created.page2.json b/fixtures/github/issues.pull_requests.created.page2.json index 98f4385..46466c5 100644 --- a/fixtures/github/issues.pull_requests.created.page2.json +++ b/fixtures/github/issues.pull_requests.created.page2.json @@ -12,7 +12,7 @@ "state": "closed", "state_reason": "not_planned", "title": "docs(deploy): document manual failover steps", - "body": "## Context\n\nDocuments the manual failover sequence for the staging stack while the automated path is still unstable.\n\n### Draft runbook\n1. Put the primary deployment in maintenance mode.\n2. Promote the standby database.\n3. Repoint the app workers and warm the cache.\n4. Verify health checks before reopening traffic.\n\n| Step | Owner | State |\n| --- | --- | --- |\n| Promote standby | SRE | Drafted |\n| Repoint workers | App platform | Drafted |\n| DNS validation | Release lead | Pending |\n\n### Risks\n- secrets rotation is still manual\n- rollback steps need screenshots\n- [ ] add the final DNS validation command", + "body": "## Context\n\nDocuments the manual failover sequence for the staging stack while the automated path is still unstable.\n\n### Draft runbook\n1. Put the primary deployment in maintenance mode.\n2. Promote the standby database.\n3. Repoint the app workers and warm the cache.\n4. Verify health checks before reopening traffic.\n\n| Step | Owner | State |\n| --- | --- | --- |\n| Promote standby | SRE | Drafted |\n| Repoint workers | App platform | Drafted |\n| DNS validation | Release lead | Pending |\n\n### Risks\n- secrets rotation is still manual\n- rollback steps need screenshots\n- add the final DNS validation command", "body_text": null, "body_html": null, "user": { diff --git a/src/component/markdown.rs b/src/component/markdown.rs index eb7e693..366ea37 100644 --- a/src/component/markdown.rs +++ b/src/component/markdown.rs @@ -2,7 +2,9 @@ use std::sync::{Arc, LazyLock}; -use gpui::{AppContext, FontWeight, ParentElement, Styled, div, relative, rems}; +use gpui::{ + AppContext, FontWeight, ParentElement, Styled, div, prelude::FluentBuilder, relative, rems, +}; use crate::{ app, @@ -108,6 +110,7 @@ enum ContentBlock { Paragraph { decoration: Option, content: RichTextContent, + has_padding: bool, }, Empty, Table { @@ -159,6 +162,7 @@ impl MarkdownText { cursor.goto_first_child(); let mut is_first_heading = true; + let mut last_node_kind_id: u16 = 0; fn build_rich_text_for_node( cursor: &mut tree_sitter::TreeCursor, @@ -187,14 +191,10 @@ impl MarkdownText { match node.kind_id() { | MARKDOWN_KIND_ID_TEXT => { - println!( - "current node start byte {} parent node start byte {}", - node.start_byte(), - node_start_byte - ); - if let Some(t) = node.utf8_text(content.as_ref()).ok() { - builder.push_text(t, style); - } + let start = node.start_byte() + byte_offset; + let end = node.end_byte(); + let text = &content[start..end]; + builder.push_text(text, style); } | MARKDOWN_KIND_ID_EMPHASIS => { @@ -331,6 +331,7 @@ impl MarkdownText { ContentBlock::Paragraph { decoration: Some(list_marker_char.clone()), content: builder.build(), + has_padding: false, } } else { // empty block @@ -397,7 +398,7 @@ impl MarkdownText { | MARKDOWN_KIND_ID_ATX_H2_MARKER => ContentBlock::Heading { font_size: rems(1.5), font_weight: gpui::FontWeight::BOLD, - mt: rems(1.5), + mt: rems(4.), mb: rems(1.), content, }, @@ -428,6 +429,12 @@ impl MarkdownText { if is_first_heading { is_first_heading = false; + match block { + | ContentBlock::Heading { ref mut mt, .. } => { + *mt = rems(0.); + } + | _ => {} + } } cursor.goto_parent(); @@ -443,6 +450,7 @@ impl MarkdownText { self.blocks.push(ContentBlock::Paragraph { decoration: None, content: builder.build(), + has_padding: last_node_kind_id != MARKDOWN_KIND_ID_ATX_HEADING, }); } @@ -493,32 +501,111 @@ impl MarkdownText { self.blocks.push(ContentBlock::Code { content }); } - // | MARKDOWN_KIND_ID_TABLE => { - // cursor.goto_first_child(); - // debug_assert!(cursor.node().kind_id() == MARKDOWN_KIND_ID_TABLE_HEADER_ROW); + | MARKDOWN_KIND_ID_TABLE => { + cursor.goto_first_child(); + debug_assert!(cursor.node().kind_id() == MARKDOWN_KIND_ID_TABLE_HEADER_ROW); - // let col_count = cursor.node().child_count(); - // // markdown tables aren't usually that big - // // lets assume the average markdown table has 10 rows (inc header) - // // preallocate the vec with capacity row * col, should be big enough to avoid realloc - // let min_row_count = 10; + let col_count = cursor.node().child_count(); + // markdown tables aren't usually that big + // lets assume the average markdown table has 10 rows (inc header) + // preallocate the vec with capacity row * col, should be big enough to avoid realloc + let min_row_count = 10; - // // cell text blocks are stored in row-major order - // let cell_blocks: Vec = Vec::with_capacity(col_count * min_row_count); + // cell text blocks are stored in row-major order + let mut cell_blocks: Vec = + Vec::with_capacity(col_count * min_row_count); + let mut builder = RichTextContentBuilder::new(); - // cursor.goto_first_child(); - // debug_assert!(cursor.node().kind_id() == MARKDOWN_KIND_ID_TABLE_CELL); + cursor.goto_first_child(); + debug_assert!(cursor.node().kind_id() == MARKDOWN_KIND_ID_TABLE_CELL); - // loop { - // let cell_node = cursor.node(); - // let cell_text_block = rich_text_for_node(&mut cursor, &self.content, 1, theme); - // cell_blocks.push(ContentBlock::Paragraph(cell_text_block)); + // construct the header row first + loop { + build_rich_text_for_node( + &mut cursor, + &mut builder, + &self.content, + 1, + theme, + Some(gpui::HighlightStyle { + font_weight: Some(gpui::FontWeight::BOLD), + ..Default::default() + }), + ); + + cell_blocks.push(builder.build()); + builder.clear(); + + if !cursor.goto_next_sibling() { + break; + } + } + + cursor.goto_parent(); + + cursor.goto_next_sibling(); + debug_assert!(cursor.node().kind_id() == MARKDOWN_KIND_ID_TABLE_DELIMITER_ROW); + + let mut row_count = 1; + loop { + if !cursor.goto_next_sibling() { + break; + } + + let row_node = cursor.node(); + if row_node.kind_id() != MARKDOWN_KIND_ID_TABLE_DATA_ROW { + break; + } + + row_count += 1; + + if !cursor.goto_first_child() { + continue; + } + debug_assert!(cursor.node().kind_id() == MARKDOWN_KIND_ID_TABLE_CELL); + + let mut current_col_count = 0; + loop { + build_rich_text_for_node( + &mut cursor, + &mut builder, + &self.content, + 0, + theme, + None, + ); + + cell_blocks.push(builder.build()); + current_col_count += 1; + + if !cursor.goto_next_sibling() { + break; + } + + builder.clear(); + } + + cursor.goto_parent(); + + // if there is fewer cells in this row than the header row + // fill in the gap + for _ in 0..(col_count - current_col_count) { + cell_blocks.push(RichTextContent::default()); + } + } + + debug_assert!(row_count * col_count == cell_blocks.len()); + + // the table consists of only the header row + self.blocks.push(ContentBlock::Table { + row_count, + col_count, + cells: cell_blocks, + }); + + cursor.goto_parent(); + } - // if !cursor.goto_next_sibling() { - // break; - // } - // } - // } | _ => { println!( "[WARN] formatting not implemenetd for node type {:?}", @@ -531,10 +618,13 @@ impl MarkdownText { self.blocks.push(ContentBlock::Paragraph { decoration: None, content: builder.build(), + has_padding: true, }); } } + last_node_kind_id = current_node.kind_id(); + if !cursor.goto_next_sibling() { break; } @@ -550,65 +640,81 @@ impl gpui::Render for MarkdownText { ) -> impl gpui::prelude::IntoElement { let theme = app::current_theme(cx); - let children = self - .blocks - .iter() - .enumerate() - .map(|(i, block)| match block { - | ContentBlock::Heading { - font_size, - font_weight, - mt, - mb, - content, - } => div() - .min_w_0() - .mt(gpui::Length::from(*mt)) - .mb(gpui::Length::from(*mb)) - .text_size(gpui::AbsoluteLength::from(*font_size)) - .font_weight(*font_weight) - .child(rich_text(content.clone())), + let children = self.blocks.iter().map(|block| match block { + | ContentBlock::Heading { + font_size, + font_weight, + mt, + mb, + content, + } => div() + .min_w_0() + .mt(gpui::Length::from(*mt)) + .mb(gpui::Length::from(*mb)) + .text_size(gpui::AbsoluteLength::from(*font_size)) + .font_weight(*font_weight) + .child(rich_text(content.clone())), - | ContentBlock::Paragraph { - decoration, - content, - } => match decoration { - | None => div().min_w_0().child(rich_text(content.clone())), - | Some(decoration) => div() + | ContentBlock::Paragraph { + decoration, + content, + has_padding, + } => match decoration { + | None => div().min_w_0().child(rich_text(content.clone())), + | Some(decoration) => div() + .w_full() + .flex() + .flex_row() + .gap_2() + .items_start() + .text_color(theme.colors.text) + .child(decoration.clone()) + .child(div().flex_1().min_w_0().child(rich_text(content.clone()))), + } + .when(*has_padding, |it| it.py_4()), + + | ContentBlock::Code { content } => div() + .min_w_0() + .w_full() + .text_sm() + .text_color(theme.colors.text) + .line_height(relative(1.2)) + .font_family("Menlo") + .px_3() + .py_2() + .rounded_sm() + .bg(theme.colors.code_bg) + .border_1() + .my_4() + .border_color(theme.colors.code_border) + .child(content.clone()), + + | ContentBlock::Table { + row_count, + col_count, + cells, + } => div().flex().w_full().child( + div() .w_full() - .flex() - .flex_row() - .gap_2() - .items_start() - .text_color(theme.colors.text) - .child(decoration.clone()) - .child(div().flex_1().min_w_0().child(rich_text(content.clone()))), - }, + .grid() + .grid_cols(*col_count as u16) + .grid_rows(*row_count as u16) + .h_40() + .border_l_1() + .border_t_1() + .border_color(theme.colors.border_muted) + .children(cells.iter().map(|cell_content| { + div() + .p_1() + .border_r_1() + .border_b_1() + .border_color(theme.colors.border_muted) + .child(rich_text(cell_content.clone())) + })), + ), - | ContentBlock::Code { content } => div() - .min_w_0() - .w_full() - .text_sm() - .text_color(theme.colors.text) - .line_height(relative(1.2)) - .font_family("Menlo") - .px_3() - .py_2() - .rounded_sm() - .bg(theme.colors.code_bg) - .border_1() - .my_4() - .border_color(theme.colors.code_border) - .child(content.clone()), - - | ContentBlock::Table { - row_count, - col_count, - cells, - } => div(), - - | ContentBlock::Empty => div(), - }); + | ContentBlock::Empty => div(), + }); div().w_full().children(children) } diff --git a/src/component/rich_text.rs b/src/component/rich_text.rs index 0e217f5..f8eaa92 100644 --- a/src/component/rich_text.rs +++ b/src/component/rich_text.rs @@ -9,7 +9,7 @@ pub(crate) struct RichTextContentBuilder { annotations: Vec, } -#[derive(Clone)] +#[derive(Clone, Default)] pub(crate) struct RichTextContent { elements: Rc<[RichTextElement]>, links: Rc<[gpui::SharedString]>, @@ -88,7 +88,7 @@ impl RichTextContentBuilder { }); } - pub(crate) fn build(self) -> RichTextContent { + pub(crate) fn build(&self) -> RichTextContent { let mut text_start = 0; let mut text_end = 0; @@ -98,10 +98,10 @@ impl RichTextContentBuilder { let mut elements: Vec = Vec::new(); let mut link_i_offset = 0; - for annotation in self.annotations { + for annotation in &self.annotations { match annotation { | Annotation::Text { style, range } => { - highlights.push(((range.start - text_start)..(range.end - text_start), style)); + highlights.push(((range.start - text_start)..(range.end - text_start), *style)); text_end = range.end; } | Annotation::Link { src, range } => { @@ -115,7 +115,7 @@ impl RichTextContentBuilder { ..Default::default() }, )); - links.push(src); + links.push(src.clone()); link_ranges.push((range.start - text_start)..(range.end - text_start)); text_end = range.end; @@ -151,6 +151,11 @@ impl RichTextContentBuilder { links: Rc::from(links), } } + + pub(crate) fn clear(&mut self) { + self.annotations.clear(); + self.raw_content.clear(); + } } impl gpui::RenderOnce for RichText {