feat: impl md table rendering
This commit is contained in:
@@ -14,5 +14,5 @@
|
|||||||
"head_branch_name": "feat/worker-context-envelope",
|
"head_branch_name": "feat/worker-context-envelope",
|
||||||
"head_repo_slug": "kennethnym/agent-tooling",
|
"head_repo_slug": "kennethnym/agent-tooling",
|
||||||
"head_ref": "4a8df12be732c0f9e5d194cd2af7430c0d2fb8d4",
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,5 +14,5 @@
|
|||||||
"head_branch_name": "chore/dashboard-spacing-scale",
|
"head_branch_name": "chore/dashboard-spacing-scale",
|
||||||
"head_repo_slug": "kennethnym/design-notes",
|
"head_repo_slug": "kennethnym/design-notes",
|
||||||
"head_ref": "5b0cf338ec46d581af0d582da6427a3dfbce9018",
|
"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."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,5 +14,5 @@
|
|||||||
"head_branch_name": "docs/manual-failover-steps",
|
"head_branch_name": "docs/manual-failover-steps",
|
||||||
"head_repo_slug": "kennethnym/infra-scripts",
|
"head_repo_slug": "kennethnym/infra-scripts",
|
||||||
"head_ref": "6fd11baf0d9d53d18f6d7b7dc265d9b09e6f4217",
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,5 +14,5 @@
|
|||||||
"head_branch_name": "feat/cached-issue-pane",
|
"head_branch_name": "feat/cached-issue-pane",
|
||||||
"head_repo_slug": "kennethnym/novem",
|
"head_repo_slug": "kennethnym/novem",
|
||||||
"head_ref": "2bc41de7731b9ef48f7d64ee9f0d5f497dbe0a51",
|
"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."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,5 +14,5 @@
|
|||||||
"head_branch_name": "feat/cached-repo-picker",
|
"head_branch_name": "feat/cached-repo-picker",
|
||||||
"head_repo_slug": "kennethnym/novem",
|
"head_repo_slug": "kennethnym/novem",
|
||||||
"head_ref": "13af7d0b48a6ce0b22d48c9b6c1c78dfcd94e6a0",
|
"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."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,5 +14,5 @@
|
|||||||
"head_branch_name": "feat/release-handoff-checklist",
|
"head_branch_name": "feat/release-handoff-checklist",
|
||||||
"head_repo_slug": "kennethnym/sprint-planner",
|
"head_repo_slug": "kennethnym/sprint-planner",
|
||||||
"head_ref": "be7a8114a57f3e9d214cb9af457c10fd6c5a0b21",
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"state": "open",
|
"state": "open",
|
||||||
"state_reason": null,
|
"state_reason": null,
|
||||||
"title": "feat(dashboard): hydrate issue pane from cached query state",
|
"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_text": "Wires the dashboard issue list to the query store and keeps selection stable while refetching.",
|
||||||
"body_html": null,
|
"body_html": null,
|
||||||
"user": {
|
"user": {
|
||||||
@@ -230,7 +230,7 @@
|
|||||||
"state": "open",
|
"state": "open",
|
||||||
"state_reason": null,
|
"state_reason": null,
|
||||||
"title": "feat(repo): add cached repository query for titlebar picker",
|
"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_text": "Introduces a repository list query so the titlebar can switch context without hitting GitHub repeatedly.",
|
||||||
"body_html": null,
|
"body_html": null,
|
||||||
"user": {
|
"user": {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"state": "open",
|
"state": "open",
|
||||||
"state_reason": null,
|
"state_reason": null,
|
||||||
"title": "chore(tokens): tighten dashboard spacing scale",
|
"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_text": "Normalizes horizontal gutters and sidebar section padding before the visual refresh.",
|
||||||
"body_html": null,
|
"body_html": null,
|
||||||
"user": {
|
"user": {
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"state": "closed",
|
"state": "closed",
|
||||||
"state_reason": "not_planned",
|
"state_reason": "not_planned",
|
||||||
"title": "docs(deploy): document manual failover steps",
|
"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_text": null,
|
||||||
"body_html": null,
|
"body_html": null,
|
||||||
"user": {
|
"user": {
|
||||||
|
|||||||
@@ -246,7 +246,7 @@
|
|||||||
"state": "open",
|
"state": "open",
|
||||||
"state_reason": null,
|
"state_reason": null,
|
||||||
"title": "chore(tokens): tighten dashboard spacing scale",
|
"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_text": "Normalizes horizontal gutters and sidebar section padding before the visual refresh.",
|
||||||
"body_html": null,
|
"body_html": null,
|
||||||
"user": {
|
"user": {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"state": "open",
|
"state": "open",
|
||||||
"state_reason": null,
|
"state_reason": null,
|
||||||
"title": "feat(repo): add cached repository query for titlebar picker",
|
"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_text": "Introduces a repository list query so the titlebar can switch context without hitting GitHub repeatedly.",
|
||||||
"body_html": null,
|
"body_html": null,
|
||||||
"user": {
|
"user": {
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
"state": "open",
|
"state": "open",
|
||||||
"state_reason": null,
|
"state_reason": null,
|
||||||
"title": "feat(dashboard): hydrate issue pane from cached query state",
|
"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_text": "Wires the dashboard issue list to the query store and keeps selection stable while refetching.",
|
||||||
"body_html": null,
|
"body_html": null,
|
||||||
"user": {
|
"user": {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"state": "closed",
|
"state": "closed",
|
||||||
"state_reason": "not_planned",
|
"state_reason": "not_planned",
|
||||||
"title": "docs(deploy): document manual failover steps",
|
"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_text": null,
|
||||||
"body_html": null,
|
"body_html": null,
|
||||||
"user": {
|
"user": {
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
use std::sync::{Arc, LazyLock};
|
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::{
|
use crate::{
|
||||||
app,
|
app,
|
||||||
@@ -108,6 +110,7 @@ enum ContentBlock {
|
|||||||
Paragraph {
|
Paragraph {
|
||||||
decoration: Option<String>,
|
decoration: Option<String>,
|
||||||
content: RichTextContent,
|
content: RichTextContent,
|
||||||
|
has_padding: bool,
|
||||||
},
|
},
|
||||||
Empty,
|
Empty,
|
||||||
Table {
|
Table {
|
||||||
@@ -159,6 +162,7 @@ impl MarkdownText {
|
|||||||
cursor.goto_first_child();
|
cursor.goto_first_child();
|
||||||
|
|
||||||
let mut is_first_heading = true;
|
let mut is_first_heading = true;
|
||||||
|
let mut last_node_kind_id: u16 = 0;
|
||||||
|
|
||||||
fn build_rich_text_for_node(
|
fn build_rich_text_for_node(
|
||||||
cursor: &mut tree_sitter::TreeCursor,
|
cursor: &mut tree_sitter::TreeCursor,
|
||||||
@@ -187,14 +191,10 @@ impl MarkdownText {
|
|||||||
|
|
||||||
match node.kind_id() {
|
match node.kind_id() {
|
||||||
| MARKDOWN_KIND_ID_TEXT => {
|
| MARKDOWN_KIND_ID_TEXT => {
|
||||||
println!(
|
let start = node.start_byte() + byte_offset;
|
||||||
"current node start byte {} parent node start byte {}",
|
let end = node.end_byte();
|
||||||
node.start_byte(),
|
let text = &content[start..end];
|
||||||
node_start_byte
|
builder.push_text(text, style);
|
||||||
);
|
|
||||||
if let Some(t) = node.utf8_text(content.as_ref()).ok() {
|
|
||||||
builder.push_text(t, style);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
| MARKDOWN_KIND_ID_EMPHASIS => {
|
| MARKDOWN_KIND_ID_EMPHASIS => {
|
||||||
@@ -331,6 +331,7 @@ impl MarkdownText {
|
|||||||
ContentBlock::Paragraph {
|
ContentBlock::Paragraph {
|
||||||
decoration: Some(list_marker_char.clone()),
|
decoration: Some(list_marker_char.clone()),
|
||||||
content: builder.build(),
|
content: builder.build(),
|
||||||
|
has_padding: false,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// empty block
|
// empty block
|
||||||
@@ -397,7 +398,7 @@ impl MarkdownText {
|
|||||||
| MARKDOWN_KIND_ID_ATX_H2_MARKER => ContentBlock::Heading {
|
| MARKDOWN_KIND_ID_ATX_H2_MARKER => ContentBlock::Heading {
|
||||||
font_size: rems(1.5),
|
font_size: rems(1.5),
|
||||||
font_weight: gpui::FontWeight::BOLD,
|
font_weight: gpui::FontWeight::BOLD,
|
||||||
mt: rems(1.5),
|
mt: rems(4.),
|
||||||
mb: rems(1.),
|
mb: rems(1.),
|
||||||
content,
|
content,
|
||||||
},
|
},
|
||||||
@@ -428,6 +429,12 @@ impl MarkdownText {
|
|||||||
|
|
||||||
if is_first_heading {
|
if is_first_heading {
|
||||||
is_first_heading = false;
|
is_first_heading = false;
|
||||||
|
match block {
|
||||||
|
| ContentBlock::Heading { ref mut mt, .. } => {
|
||||||
|
*mt = rems(0.);
|
||||||
|
}
|
||||||
|
| _ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor.goto_parent();
|
cursor.goto_parent();
|
||||||
@@ -443,6 +450,7 @@ impl MarkdownText {
|
|||||||
self.blocks.push(ContentBlock::Paragraph {
|
self.blocks.push(ContentBlock::Paragraph {
|
||||||
decoration: None,
|
decoration: None,
|
||||||
content: builder.build(),
|
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 });
|
self.blocks.push(ContentBlock::Code { content });
|
||||||
}
|
}
|
||||||
|
|
||||||
// | MARKDOWN_KIND_ID_TABLE => {
|
| MARKDOWN_KIND_ID_TABLE => {
|
||||||
// cursor.goto_first_child();
|
cursor.goto_first_child();
|
||||||
// debug_assert!(cursor.node().kind_id() == MARKDOWN_KIND_ID_TABLE_HEADER_ROW);
|
debug_assert!(cursor.node().kind_id() == MARKDOWN_KIND_ID_TABLE_HEADER_ROW);
|
||||||
|
|
||||||
// let col_count = cursor.node().child_count();
|
let col_count = cursor.node().child_count();
|
||||||
// // markdown tables aren't usually that big
|
// markdown tables aren't usually that big
|
||||||
// // lets assume the average markdown table has 10 rows (inc header)
|
// 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
|
// preallocate the vec with capacity row * col, should be big enough to avoid realloc
|
||||||
// let min_row_count = 10;
|
let min_row_count = 10;
|
||||||
|
|
||||||
// // cell text blocks are stored in row-major order
|
// cell text blocks are stored in row-major order
|
||||||
// let cell_blocks: Vec<ContentBlock> = Vec::with_capacity(col_count * min_row_count);
|
let mut cell_blocks: Vec<RichTextContent> =
|
||||||
|
Vec::with_capacity(col_count * min_row_count);
|
||||||
|
let mut builder = RichTextContentBuilder::new();
|
||||||
|
|
||||||
// cursor.goto_first_child();
|
cursor.goto_first_child();
|
||||||
// debug_assert!(cursor.node().kind_id() == MARKDOWN_KIND_ID_TABLE_CELL);
|
debug_assert!(cursor.node().kind_id() == MARKDOWN_KIND_ID_TABLE_CELL);
|
||||||
|
|
||||||
// loop {
|
// construct the header row first
|
||||||
// let cell_node = cursor.node();
|
loop {
|
||||||
// let cell_text_block = rich_text_for_node(&mut cursor, &self.content, 1, theme);
|
build_rich_text_for_node(
|
||||||
// cell_blocks.push(ContentBlock::Paragraph(cell_text_block));
|
&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!(
|
println!(
|
||||||
"[WARN] formatting not implemenetd for node type {:?}",
|
"[WARN] formatting not implemenetd for node type {:?}",
|
||||||
@@ -531,10 +618,13 @@ impl MarkdownText {
|
|||||||
self.blocks.push(ContentBlock::Paragraph {
|
self.blocks.push(ContentBlock::Paragraph {
|
||||||
decoration: None,
|
decoration: None,
|
||||||
content: builder.build(),
|
content: builder.build(),
|
||||||
|
has_padding: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
last_node_kind_id = current_node.kind_id();
|
||||||
|
|
||||||
if !cursor.goto_next_sibling() {
|
if !cursor.goto_next_sibling() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -550,65 +640,81 @@ impl gpui::Render for MarkdownText {
|
|||||||
) -> impl gpui::prelude::IntoElement {
|
) -> impl gpui::prelude::IntoElement {
|
||||||
let theme = app::current_theme(cx);
|
let theme = app::current_theme(cx);
|
||||||
|
|
||||||
let children = self
|
let children = self.blocks.iter().map(|block| match block {
|
||||||
.blocks
|
| ContentBlock::Heading {
|
||||||
.iter()
|
font_size,
|
||||||
.enumerate()
|
font_weight,
|
||||||
.map(|(i, block)| match block {
|
mt,
|
||||||
| ContentBlock::Heading {
|
mb,
|
||||||
font_size,
|
content,
|
||||||
font_weight,
|
} => div()
|
||||||
mt,
|
.min_w_0()
|
||||||
mb,
|
.mt(gpui::Length::from(*mt))
|
||||||
content,
|
.mb(gpui::Length::from(*mb))
|
||||||
} => div()
|
.text_size(gpui::AbsoluteLength::from(*font_size))
|
||||||
.min_w_0()
|
.font_weight(*font_weight)
|
||||||
.mt(gpui::Length::from(*mt))
|
.child(rich_text(content.clone())),
|
||||||
.mb(gpui::Length::from(*mb))
|
|
||||||
.text_size(gpui::AbsoluteLength::from(*font_size))
|
|
||||||
.font_weight(*font_weight)
|
|
||||||
.child(rich_text(content.clone())),
|
|
||||||
|
|
||||||
| ContentBlock::Paragraph {
|
| ContentBlock::Paragraph {
|
||||||
decoration,
|
decoration,
|
||||||
content,
|
content,
|
||||||
} => match decoration {
|
has_padding,
|
||||||
| None => div().min_w_0().child(rich_text(content.clone())),
|
} => match decoration {
|
||||||
| Some(decoration) => div()
|
| 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()
|
.w_full()
|
||||||
.flex()
|
.grid()
|
||||||
.flex_row()
|
.grid_cols(*col_count as u16)
|
||||||
.gap_2()
|
.grid_rows(*row_count as u16)
|
||||||
.items_start()
|
.h_40()
|
||||||
.text_color(theme.colors.text)
|
.border_l_1()
|
||||||
.child(decoration.clone())
|
.border_t_1()
|
||||||
.child(div().flex_1().min_w_0().child(rich_text(content.clone()))),
|
.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()
|
| ContentBlock::Empty => 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(),
|
|
||||||
});
|
|
||||||
|
|
||||||
div().w_full().children(children)
|
div().w_full().children(children)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ pub(crate) struct RichTextContentBuilder {
|
|||||||
annotations: Vec<Annotation>,
|
annotations: Vec<Annotation>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Default)]
|
||||||
pub(crate) struct RichTextContent {
|
pub(crate) struct RichTextContent {
|
||||||
elements: Rc<[RichTextElement]>,
|
elements: Rc<[RichTextElement]>,
|
||||||
links: Rc<[gpui::SharedString]>,
|
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_start = 0;
|
||||||
let mut text_end = 0;
|
let mut text_end = 0;
|
||||||
|
|
||||||
@@ -98,10 +98,10 @@ impl RichTextContentBuilder {
|
|||||||
let mut elements: Vec<RichTextElement> = Vec::new();
|
let mut elements: Vec<RichTextElement> = Vec::new();
|
||||||
let mut link_i_offset = 0;
|
let mut link_i_offset = 0;
|
||||||
|
|
||||||
for annotation in self.annotations {
|
for annotation in &self.annotations {
|
||||||
match annotation {
|
match annotation {
|
||||||
| Annotation::Text { style, range } => {
|
| 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;
|
text_end = range.end;
|
||||||
}
|
}
|
||||||
| Annotation::Link { src, range } => {
|
| Annotation::Link { src, range } => {
|
||||||
@@ -115,7 +115,7 @@ impl RichTextContentBuilder {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
links.push(src);
|
links.push(src.clone());
|
||||||
link_ranges.push((range.start - text_start)..(range.end - text_start));
|
link_ranges.push((range.start - text_start)..(range.end - text_start));
|
||||||
|
|
||||||
text_end = range.end;
|
text_end = range.end;
|
||||||
@@ -151,6 +151,11 @@ impl RichTextContentBuilder {
|
|||||||
links: Rc::from(links),
|
links: Rc::from(links),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clear(&mut self) {
|
||||||
|
self.annotations.clear();
|
||||||
|
self.raw_content.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl gpui::RenderOnce for RichText {
|
impl gpui::RenderOnce for RichText {
|
||||||
|
|||||||
Reference in New Issue
Block a user