feat: impl md table rendering

This commit is contained in:
2026-06-06 23:19:15 +01:00
parent 5cd153ce58
commit 6f1e447747
13 changed files with 217 additions and 106 deletions

View File

@@ -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"
}

View File

@@ -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."
}

View File

@@ -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"
}

View File

@@ -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."
}

View File

@@ -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."
}

View File

@@ -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"
}

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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<String>,
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<ContentBlock> = Vec::with_capacity(col_count * min_row_count);
// cell text blocks are stored in row-major order
let mut cell_blocks: Vec<RichTextContent> =
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)
}

View File

@@ -9,7 +9,7 @@ pub(crate) struct RichTextContentBuilder {
annotations: Vec<Annotation>,
}
#[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<RichTextElement> = 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 {