cell highlighting
This commit is contained in:
+1
-1
@@ -259,7 +259,7 @@ There is no snoozing. The `snoozed_until` and `snooze_until` columns are still i
|
||||
|
||||
Every Markdown view (inbox items and the transcript) renders a line `<details><summary>Title</summary>` (or `<details>` with the `<summary>` on the next line), Markdown content, and a line `</details>` as a collapsed section (`web/src/markdown.ts`; sections nest) showing only the title; session guidance and the `ask_human` schema tell agents to put long supporting detail in them.
|
||||
|
||||
A status mark that is the whole of a table cell, paragraph line, list item or heading, optionally in bold, renders coloured (`marks` in `web/src/markdown.ts` and `mac/Sources/Byre/Markdown.swift`): checks (✓, ✅ and the like) in `--ok`, `~`, ≈ and ⚠ in `--warn`, and `x`, ×, ✗, ❌ and the like in `--danger`. A table cell holding one also gets the matching `-tint` background. A mark inside other text, or in code, stays plain.
|
||||
A status mark that is the whole of a table cell, paragraph line, list item or heading, optionally in bold, renders coloured (`marks` in `web/src/markdown.ts` and `mac/Sources/Byre/Markdown.swift`): checks (✓, ✅ and the like) in `--ok`, `~`, ≈ and ⚠ in `--warn`, `x`, ×, ✗, ❌ and the like in `--danger`, and `-`, – and — in `--muted`. A table cell whose first word is a mark, alone or followed by text (`✓ cross-vendor`), colours that mark and gets the matching `-tint` background, `--surface` for the dashes; the rest of its text stays plain. A mark inside other text, or in code, stays plain.
|
||||
|
||||
Markdown images (``, or ``; the plain form also accepts spaces, since worktree paths under `Application Support` have one) render inline, linking to the full image. A `src` that is an absolute path, a path relative to the worktree or a `file://` URL is loaded from `GET /api/tasks/{taskId}/image?path=` for the item's or event's task, which refuses (403) a path that resolves outside the task's worktree, so the `ask_human` description tells agents to copy a scratch image into the worktree first; a `data:image/` URL of PNG, JPEG, GIF or WebP is used as is; any other scheme, `http(s)` included, shows as a link with the alt text and is never fetched. An image that fails to load is replaced by "image not available: <src>". An inbox `image` attachment renders the same way from its `image` (data URL) or `path`.
|
||||
|
||||
|
||||
@@ -12,16 +12,18 @@ indirect enum MDInline: Sendable {
|
||||
}
|
||||
|
||||
enum MDMark: Sendable {
|
||||
case ok, warn, danger
|
||||
case ok, warn, danger, muted
|
||||
}
|
||||
|
||||
private let marks: [String: MDMark] = [
|
||||
"✓": .ok, "✔": .ok, "✔️": .ok, "✅": .ok, "☑": .ok, "☑️": .ok,
|
||||
"~": .warn, "≈": .warn, "〜": .warn, "⚠": .warn, "⚠️": .warn, "🟡": .warn,
|
||||
"x": .danger, "X": .danger, "×": .danger, "✗": .danger, "✘": .danger, "✕": .danger, "✖": .danger, "✖️": .danger, "❌": .danger,
|
||||
"-": .muted, "–": .muted, "—": .muted, "−": .muted,
|
||||
]
|
||||
|
||||
private let boldWrap = Re(#"^(\*\*|__)(.*)\1$"#)
|
||||
private let leadToken = Re(#"^\s*(\S*)([\s\S]*)$"#)
|
||||
|
||||
struct MDItem: Sendable {
|
||||
var choice: String?
|
||||
@@ -200,7 +202,7 @@ private func blocks(_ lines: [String], _ choices: Bool) -> [MDBlock] {
|
||||
rows.append(cells(lines[i]))
|
||||
i += 1
|
||||
}
|
||||
out.append(.table(head: rows[0].map(inline), rows: rows.dropFirst().map { $0.map(inline) }))
|
||||
out.append(.table(head: rows[0].map(cellInline), rows: rows.dropFirst().map { $0.map(cellInline) }))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -314,13 +316,29 @@ private func cells(_ line: String) -> [String] {
|
||||
}
|
||||
|
||||
private func inline(_ src: String) -> [MDInline] {
|
||||
let t = trimmed(src)
|
||||
if let mark = marks[trimmed(boldWrap.exec(t)?[2] ?? t)] {
|
||||
if let mark = markOf(src) {
|
||||
return [.mark(mark, spans(src))]
|
||||
}
|
||||
return spans(src)
|
||||
}
|
||||
|
||||
private func markOf(_ src: String) -> MDMark? {
|
||||
let t = trimmed(src)
|
||||
return marks[trimmed(boldWrap.exec(t)?[2] ?? t)]
|
||||
}
|
||||
|
||||
private func cellInline(_ src: String) -> [MDInline] {
|
||||
if markOf(src) != nil {
|
||||
return inline(src)
|
||||
}
|
||||
let g = leadToken.exec(src)!
|
||||
let lead = g[1] ?? "", rest = g[2] ?? ""
|
||||
guard let mark = markOf(lead) else {
|
||||
return spans(src)
|
||||
}
|
||||
return [.mark(mark, spans(lead))] + spans(rest)
|
||||
}
|
||||
|
||||
private func spans(_ src: String) -> [MDInline] {
|
||||
var out: [MDInline] = []
|
||||
var rest = src
|
||||
|
||||
@@ -60,6 +60,7 @@ extension MDMark {
|
||||
case .ok: Palette.ok
|
||||
case .warn: Palette.warn
|
||||
case .danger: Palette.danger
|
||||
case .muted: Palette.muted
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +69,7 @@ extension MDMark {
|
||||
case .ok: Palette.okTint
|
||||
case .warn: Palette.warnTint
|
||||
case .danger: Palette.dangerTint
|
||||
case .muted: Palette.surface
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -676,7 +678,7 @@ private struct TableBlock: View {
|
||||
}
|
||||
|
||||
private func tint(_ cell: [MDInline]) -> Color? {
|
||||
guard cell.count == 1, case .mark(let mark, _) = cell[0] else {
|
||||
guard case .mark(let mark, _) = cell.first else {
|
||||
return nil
|
||||
}
|
||||
return mark.tint
|
||||
|
||||
Vendored
+17
-5
@@ -245,7 +245,19 @@ function cells(line) {
|
||||
}
|
||||
function table(rows, opts) {
|
||||
const [head, ...body] = rows;
|
||||
return h("div", { class: "md-table" }, h("table", {}, h("thead", {}, h("tr", {}, ...head.map((c) => h("th", markClass(c), ...inline(c, opts))))), h("tbody", {}, ...body.map((r) => h("tr", {}, ...r.map((c) => h("td", markClass(c), ...inline(c, opts))))))));
|
||||
return h("div", { class: "md-table" }, h("table", {}, h("thead", {}, h("tr", {}, ...head.map((c) => cell("th", c, opts)))), h("tbody", {}, ...body.map((r) => h("tr", {}, ...r.map((c) => cell("td", c, opts)))))));
|
||||
}
|
||||
function cell(tag, src, opts) {
|
||||
const whole = markOf(src);
|
||||
if (whole) {
|
||||
return h(tag, { class: `md-mark-cell ${whole}` }, ...inline(src, opts));
|
||||
}
|
||||
const [, lead = "", rest = ""] = /^\s*(\S*)(.*)$/s.exec(src);
|
||||
const mark = markOf(lead);
|
||||
if (!mark) {
|
||||
return h(tag, {}, ...spans(src, opts));
|
||||
}
|
||||
return h(tag, { class: `md-mark-cell ${mark}` }, h("span", { class: `md-mark ${mark}` }, ...spans(lead, opts)), ...spans(rest, opts));
|
||||
}
|
||||
const marks = {
|
||||
"✓": "ok",
|
||||
@@ -269,14 +281,14 @@ const marks = {
|
||||
"✖": "danger",
|
||||
"✖️": "danger",
|
||||
"❌": "danger",
|
||||
"-": "none",
|
||||
"–": "none",
|
||||
"—": "none",
|
||||
"−": "none",
|
||||
};
|
||||
function markOf(src) {
|
||||
return marks[src.trim().replace(/^(\*\*|__)(.*)\1$/, "$2").trim()];
|
||||
}
|
||||
function markClass(cell) {
|
||||
const mark = markOf(cell);
|
||||
return mark ? { class: `md-mark ${mark}` } : {};
|
||||
}
|
||||
const inlineToken = /`([^`]+)`|\*\*(.+?)\*\*|__(.+?)__|\*([^*\s][^*]*?)\*|(?<![\w])_([^_\s][^_]*?)_(?![\w])|!\[([^\]]*)\]\((?:<([^>]+)>|([^)]+))\)|\[([^\]]+)\]\(([^)\s]+)\)|(https?:\/\/[^\s<>()]+[^\s<>().,;:!?'"])/;
|
||||
function inline(src, opts) {
|
||||
const mark = markOf(src);
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+19
-7
@@ -283,12 +283,25 @@ function table(rows: string[][], opts: Options): HTMLElement {
|
||||
h(
|
||||
"table",
|
||||
{},
|
||||
h("thead", {}, h("tr", {}, ...head!.map((c) => h("th", markClass(c), ...inline(c, opts))))),
|
||||
h("tbody", {}, ...body.map((r) => h("tr", {}, ...r.map((c) => h("td", markClass(c), ...inline(c, opts)))))),
|
||||
h("thead", {}, h("tr", {}, ...head!.map((c) => cell("th", c, opts)))),
|
||||
h("tbody", {}, ...body.map((r) => h("tr", {}, ...r.map((c) => cell("td", c, opts))))),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function cell(tag: "th" | "td", src: string, opts: Options): HTMLElement {
|
||||
const whole = markOf(src);
|
||||
if (whole) {
|
||||
return h(tag, { class: `md-mark-cell ${whole}` }, ...inline(src, opts));
|
||||
}
|
||||
const [, lead = "", rest = ""] = /^\s*(\S*)(.*)$/s.exec(src)!;
|
||||
const mark = markOf(lead);
|
||||
if (!mark) {
|
||||
return h(tag, {}, ...spans(src, opts));
|
||||
}
|
||||
return h(tag, { class: `md-mark-cell ${mark}` }, h("span", { class: `md-mark ${mark}` }, ...spans(lead, opts)), ...spans(rest, opts));
|
||||
}
|
||||
|
||||
const marks: Record<string, string> = {
|
||||
"✓": "ok",
|
||||
"✔": "ok",
|
||||
@@ -311,17 +324,16 @@ const marks: Record<string, string> = {
|
||||
"✖": "danger",
|
||||
"✖️": "danger",
|
||||
"❌": "danger",
|
||||
"-": "none",
|
||||
"–": "none",
|
||||
"—": "none",
|
||||
"−": "none",
|
||||
};
|
||||
|
||||
function markOf(src: string): string | undefined {
|
||||
return marks[src.trim().replace(/^(\*\*|__)(.*)\1$/, "$2").trim()];
|
||||
}
|
||||
|
||||
function markClass(cell: string): { class?: string } {
|
||||
const mark = markOf(cell);
|
||||
return mark ? { class: `md-mark ${mark}` } : {};
|
||||
}
|
||||
|
||||
const inlineToken =
|
||||
/`([^`]+)`|\*\*(.+?)\*\*|__(.+?)__|\*([^*\s][^*]*?)\*|(?<![\w])_([^_\s][^_]*?)_(?![\w])|!\[([^\]]*)\]\((?:<([^>]+)>|([^)]+))\)|\[([^\]]+)\]\(([^)\s]+)\)|(https?:\/\/[^\s<>()]+[^\s<>().,;:!?'"])/;
|
||||
|
||||
|
||||
+11
-3
@@ -862,18 +862,26 @@ button.compact.danger {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.md .md-mark.ok:is(td, th) {
|
||||
.md-mark.none {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.md .md-mark-cell.ok {
|
||||
background: var(--ok-tint);
|
||||
}
|
||||
|
||||
.md .md-mark.warn:is(td, th) {
|
||||
.md .md-mark-cell.warn {
|
||||
background: var(--warn-tint);
|
||||
}
|
||||
|
||||
.md .md-mark.danger:is(td, th) {
|
||||
.md .md-mark-cell.danger {
|
||||
background: var(--danger-tint);
|
||||
}
|
||||
|
||||
.md .md-mark-cell.none {
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.msg.user {
|
||||
align-self: flex-end;
|
||||
max-width: 85%;
|
||||
|
||||
Reference in New Issue
Block a user