From 22b84f6ed6ee550260b6d786b43877dab2b341b1 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 21 Oct 2026 23:12:55 +0000 Subject: [PATCH 1/7] test(web): user messages keep single tildes in paths (red) Reproduces #16105: a user message containing ~/a and (~/b renders the span between them as strikethrough and hides both tildes. Guards that ~~double~~ still strikes and assistant rendering is unchanged. Co-Authored-By: Claude Opus 5.5 --- .../chat/MessagesTimeline.browser.test.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/web/src/components/chat/MessagesTimeline.browser.test.tsx b/apps/web/src/components/chat/MessagesTimeline.browser.test.tsx index bc04aeeb4d502..f69b8ab97008c 100644 --- a/apps/web/src/components/chat/MessagesTimeline.browser.test.tsx +++ b/apps/web/src/components/chat/MessagesTimeline.browser.test.tsx @@ -150,6 +150,25 @@ describe("MessagesTimeline messages", () => { expect((globalThis as { __t3Xss?: number }).__t3Xss).toBeUndefined(); }); + it("keeps single tildes in user paths literal without changing double tildes or assistant text", async () => { + const source = + "Install the skill as ~/folder-a/skills/x, then follow the guide (~/folder-a/guide.md)."; + await using screen = await renderTimeline([ + message(source), + message("Drop ~~the old plan~~", {}, "message-2"), + message(source, { role: "assistant" }, "assistant-1"), + ]); + const [user, doubleTilde, assistant] = screen.container.querySelectorAll( + "[data-timeline-row-kind=message]", + ); + expect(user?.textContent).toContain(source); + expect(user?.querySelector("del")).toBeNull(); + expect(doubleTilde?.querySelector("del")?.textContent).toBe("the old plan"); + expect(assistant?.querySelector("del")?.textContent).toBe( + "/folder-a/skills/x, then follow the guide (", + ); + }); + it("renders sanitized raw HTML in assistant messages", async () => { await using screen = await renderTimeline([ message( From 427fee4d7ce3148f3a87a359ce213a1c9b34b1b3 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 21 Oct 2026 23:23:52 +0000 Subject: [PATCH 2/7] fix(web): user messages keep single tildes in paths A user message like "~/a/x, then (~/a/guide.md)" rendered the span between the tildes as GFM single-tilde strikethrough and hid both tildes. User messages and their thread-find extraction now parse with remark-gfm's singleTilde disabled; ~~double~~ still strikes, and assistant rendering and stored message text are unchanged. Fixes #16105 Co-Authored-By: Claude Opus 5.5 --- apps/web/src/components/chat/TimelineUserMessageRow.tsx | 2 ++ packages/shared/src/markdownPipeline.ts | 7 +++++++ packages/shared/src/threadFindText.test.ts | 8 ++++++++ packages/shared/src/threadFindText.ts | 2 ++ 4 files changed, 19 insertions(+) diff --git a/apps/web/src/components/chat/TimelineUserMessageRow.tsx b/apps/web/src/components/chat/TimelineUserMessageRow.tsx index 2f2f2063a4123..628bc18230529 100644 --- a/apps/web/src/components/chat/TimelineUserMessageRow.tsx +++ b/apps/web/src/components/chat/TimelineUserMessageRow.tsx @@ -58,6 +58,7 @@ import { import { chatMarkdownClipboardPayload } from "../../markdown-clipboard"; import type { ChatMarkdownContextReference } from "../ChatMarkdown"; import { cn } from "~/lib/utils"; +import { CHAT_USER_MARKDOWN_REMARK_PLUGINS } from "@t3tools/shared/markdownPipeline"; import { formatChatTimestampTooltip, formatDayAwareTimestamp } from "../../timestampFormat"; import { WorkflowOpeningBlock } from "./WorkflowOpeningBlock"; import { TimelineRowCtx, TimelineRowActivityCtx, type TimelineRow } from "./timelineRowContext"; @@ -718,6 +719,7 @@ const UserMessageBody = memo(function UserMessageBody(props: { className="text-foreground" lineBreaks parseRawHtml={false} + extraRemarkPlugins={CHAT_USER_MARKDOWN_REMARK_PLUGINS} renderContextReference={props.renderContextReference} headingLevelOffset={MESSAGE_HEADING_LEVEL} /> diff --git a/packages/shared/src/markdownPipeline.ts b/packages/shared/src/markdownPipeline.ts index 767ffdf836545..d0b701a3dc205 100644 --- a/packages/shared/src/markdownPipeline.ts +++ b/packages/shared/src/markdownPipeline.ts @@ -185,6 +185,13 @@ export const CHAT_MARKDOWN_REMARK_PLUGINS_WITH_BREAKS: PluggableList = [ remarkNormalizeLinksAndTagInlineCode, ]; +// Users type paths like ~/a and (~/b, which GFM's single-tilde strikethrough turns into a hidden +// span. Appended after the base list, this reconfigures its remark-gfm entry; ~~double~~ still +// strikes. +export const CHAT_USER_MARKDOWN_REMARK_PLUGINS: PluggableList = [ + [remarkGfm, { singleTilde: false }], +]; + export const CHAT_MARKDOWN_REHYPE_PLUGINS: PluggableList = [ rehypePreserveBareAnchorPlaceholders, rehypeRaw, diff --git a/packages/shared/src/threadFindText.test.ts b/packages/shared/src/threadFindText.test.ts index 0dd6a87ae1dea..54fa8f96801a5 100644 --- a/packages/shared/src/threadFindText.test.ts +++ b/packages/shared/src/threadFindText.test.ts @@ -20,6 +20,14 @@ it("excludes review attachments rendered as context chips", () => { ]); }); +it("indexes single tildes in user paths as typed", () => { + const text = "Install as ~/a/x, then read (~/a/guide.md)."; + expect(searchableMessageSegments({ role: "user", text, streaming: false })?.join("")).toBe(text); + expect( + searchableMessageSegments({ role: "user", text: "Drop ~~old~~", streaming: false })?.join(""), + ).toBe("Drop old"); +}); + it("excludes structured context chips without joining text across them", () => { expect( searchableMessageSegments({ diff --git a/packages/shared/src/threadFindText.ts b/packages/shared/src/threadFindText.ts index a2e5b4dbb982d..42055ac057b25 100644 --- a/packages/shared/src/threadFindText.ts +++ b/packages/shared/src/threadFindText.ts @@ -33,6 +33,7 @@ import { CHAT_MARKDOWN_REHYPE_PLUGINS, CHAT_MARKDOWN_REMARK_PLUGINS, CHAT_MARKDOWN_REMARK_PLUGINS_WITH_BREAKS, + CHAT_USER_MARKDOWN_REMARK_PLUGINS, } from "./markdownPipeline.ts"; // Inline wrappers (including Shiki token spans) must not split a search phrase. @@ -88,6 +89,7 @@ const assistantBreaksProcessor = unified() const userProcessor = unified() .use(remarkParse) .use(CHAT_MARKDOWN_REMARK_PLUGINS_WITH_BREAKS) + .use(CHAT_USER_MARKDOWN_REMARK_PLUGINS) .use(remarkRehype, { allowDangerousHtml: true }); interface TextTree { From 39e64ba784dcab841b45735a974d7804eaefd951 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 21 Oct 2026 23:31:39 +0000 Subject: [PATCH 3/7] test(web): add synthetic tilde evidence fixtures --- apps/server/scripts/fixtures/issue-16105.json | 38 +++++++++ .../scripts/fixtures/seed-issue-16105.py | 80 +++++++++++++++++++ packages/shared/src/markdownPipeline.ts | 4 +- 3 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 apps/server/scripts/fixtures/issue-16105.json create mode 100644 apps/server/scripts/fixtures/seed-issue-16105.py diff --git a/apps/server/scripts/fixtures/issue-16105.json b/apps/server/scripts/fixtures/issue-16105.json new file mode 100644 index 0000000000000..ecd091167963f --- /dev/null +++ b/apps/server/scripts/fixtures/issue-16105.json @@ -0,0 +1,38 @@ +{ + "threadId": "9480aec7-3adf-40ed-a5b8-1758521cd391", + "timestamp": "2026-10-21T12:00:00.000Z", + "title": "16105 synthetic markdown proof", + "contextVisual": "UNVERIFIED: no generated terminal record available; empty-record context tests extraction only", + "messages": [ + { + "fixture": "F1", + "role": "user", + "text": "Install ~/folder-a/skills/x, then read (~/folder-a/guide.md).\nKeep ~solo~ and ~~old~~ with **bold** and [guide](https://example.com).\n> Quote ~/quote/a then (~/quote/b).\n- List ~/list/a then (~/list/b).\n| A | B |\n| - | - |\n| ~/table/a | (~/table/b) |" + }, + { + "fixture": "F1A", + "role": "assistant", + "text": "Install ~/folder-a/skills/x, then read (~/folder-a/guide.md).\nKeep ~solo~ and ~~old~~ with **bold** and [guide](https://example.com).\n> Quote ~/quote/a then (~/quote/b).\n- List ~/list/a then (~/list/b).\n| A | B |\n| - | - |\n| ~/table/a | (~/table/b) |" + }, + { + "fixture": "F2", + "role": "user", + "text": "Inline `~/inline/a` and `~/inline/b`; literal html.\n```text\n~/backtick/a then (~/backtick/b).\n```\n~~~text\n~/tildefence/a then (~/tildefence/b).\n~~~\n\nTail ~/hidden/a then (~/hidden/b).\n[terminal label](t3-context://v1/terminal/terminal_1)", + "context": { + "version": 1, + "records": [] + } + }, + { + "fixture": "F3", + "role": "user", + "text": "Automation ~/auto/a then (~/auto/b). Keep ~~old~~.", + "scheduledTaskId": "task:proof-16105" + }, + { + "fixture": "F4", + "role": "user", + "text": "Legacy ~/legacy/a then (~/legacy/b).\n\n\n- Terminal 1 line 12:\n 12 | hidden output\n" + } + ] +} diff --git a/apps/server/scripts/fixtures/seed-issue-16105.py b/apps/server/scripts/fixtures/seed-issue-16105.py new file mode 100644 index 0000000000000..d5ecd97e0da94 --- /dev/null +++ b/apps/server/scripts/fixtures/seed-issue-16105.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +"""Seed stopped, isolated proof databases from a UI-created thread skeleton. + +Usage: python3 seed-issue-16105.py /absolute/worktree/.t3 [...] +Projection-only visual/search fixtures; not coherent orchestration event history. +""" +import json +import pathlib +import socket +import sqlite3 +import sys + +fixture = json.loads(pathlib.Path(__file__).with_name('issue-16105.json').read_text()) +for port in (14829, 15209): + with socket.socket() as probe: + if probe.connect_ex(('127.0.0.1', port)) == 0: + raise SystemExit(f'Stop proof server on {port} before seeding') +for argument in sys.argv[1:]: + home = pathlib.Path(argument).resolve() + if home.name != '.t3' or home.parent.parent != pathlib.Path('/home/ubuntu/wt'): + raise SystemExit('Only isolated /home/ubuntu/wt//.t3 homes are allowed') + database = home / 'userdata/statev2.sqlite' + connection = sqlite3.connect(database) + connection.row_factory = sqlite3.Row + thread = fixture['threadId'] + messages = list(connection.execute('SELECT * FROM orchestration_v2_projection_messages WHERE thread_id=? ORDER BY created_at,message_id', (thread,))) + items = list(connection.execute('SELECT * FROM orchestration_v2_projection_turn_items WHERE thread_id=? ORDER BY ordinal', (thread,))) + positions = {json.loads(row['payload_json']).get('messageId'): row['ordinal'] for row in items} + messages.sort(key=lambda row: positions[row['message_id']]) + templates = {role: json.loads(next(row['payload_json'] for row in messages if row['role'] == role)) for role in ('user', 'assistant')} + item_templates = {role: json.loads(next(row['payload_json'] for row in items if row['type'] == role + '_message')) for role in ('user', 'assistant')} + assert len(messages) >= len(fixture['messages']), 'Need five real message skeletons' + backup = database.with_suffix('.before-16105-seed.sqlite') + if not backup.exists(): + with sqlite3.connect(backup) as target: + connection.backup(target) + seeded = [] + with connection: + keep_items = [] + keep_messages = [] + for source, case in zip(messages, fixture['messages']): + source_payload = json.loads(source['payload_json']) + old_item = next(row for row in items if json.loads(row['payload_json']).get('messageId') == source['message_id']) + old_payload = json.loads(old_item['payload_json']) + role = case['role'] + message = dict(templates[role]) + item = dict(item_templates[role]) + for key in ('id', 'threadId', 'runId', 'nodeId'): + message[key] = source_payload[key] + for key in ('id', 'threadId', 'runId', 'nodeId', 'providerThreadId', 'providerTurnId', 'nativeItemRef', 'parentItemId', 'ordinal'): + item[key] = old_payload[key] + item['messageId'] = message['id'] + for payload in (message, item): + for key in ('context', 'scheduledTaskId'): + payload.pop(key, None) + if key in case: + payload[key] = case[key] + payload['text'] = case['text'] + for key in ('createdAt', 'updatedAt', 'startedAt', 'completedAt'): + if key in payload: + payload[key] = fixture['timestamp'] + message['streaming'] = False + if role == 'assistant': + item['streaming'] = False + connection.execute('UPDATE orchestration_v2_projection_messages SET role=?,streaming=0,created_at=?,updated_at=?,payload_json=? WHERE message_id=?', (role, fixture['timestamp'], fixture['timestamp'], json.dumps(message), message['id'])) + connection.execute('UPDATE orchestration_v2_projection_turn_items SET type=?,updated_at=?,payload_json=? WHERE turn_item_id=?', (role+'_message', fixture['timestamp'], json.dumps(item), item['id'])) + keep_items.append(item['id']) + keep_messages.append(message['id']) + seeded.append({'fixture':case['fixture'], 'message':message, 'turnItem':item}) + for table, column, retained in [('orchestration_v2_projection_messages','message_id',keep_messages), ('orchestration_v2_projection_turn_items','turn_item_id',keep_items), ('orchestration_v2_turn_item_positions','turn_item_id',keep_items)]: + placeholders = ','.join('?' for _ in retained) + connection.execute(f'DELETE FROM {table} WHERE thread_id=? AND {column} NOT IN ({placeholders})', [thread, *retained]) + row = connection.execute('SELECT payload_json FROM orchestration_v2_projection_threads WHERE thread_id=?', (thread,)).fetchone() + payload = json.loads(row[0]) + payload['title'] = fixture['title'] + connection.execute('UPDATE orchestration_v2_projection_threads SET title=?,payload_json=? WHERE thread_id=?', (fixture['title'],json.dumps(payload),thread)) + manifest = database.with_name('issue-16105-seeded.json') + manifest.write_text(json.dumps({'fixture':fixture,'seeded':seeded},indent=2)+'\n') + connection.close() + print(json.dumps({'database':str(database),'manifest':str(manifest),'messages':len(seeded)})) diff --git a/packages/shared/src/markdownPipeline.ts b/packages/shared/src/markdownPipeline.ts index d0b701a3dc205..6b8e5a08241f5 100644 --- a/packages/shared/src/markdownPipeline.ts +++ b/packages/shared/src/markdownPipeline.ts @@ -185,8 +185,8 @@ export const CHAT_MARKDOWN_REMARK_PLUGINS_WITH_BREAKS: PluggableList = [ remarkNormalizeLinksAndTagInlineCode, ]; -// Users type paths like ~/a and (~/b, which GFM's single-tilde strikethrough turns into a hidden -// span. Appended after the base list, this reconfigures its remark-gfm entry; ~~double~~ still +// Users type paths like ~/a and (~/b, which GFM's single-tilde strikethrough renders as a +// span with the tilde markers hidden. Appended after the base list, this reconfigures its remark-gfm entry; ~~double~~ still // strikes. export const CHAT_USER_MARKDOWN_REMARK_PLUGINS: PluggableList = [ [remarkGfm, { singleTilde: false }], From b61ad4e2b92b23dd796f8b3bfc7fb4a6a378a9bf Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 21 Oct 2026 23:34:52 +0000 Subject: [PATCH 4/7] test(web): make tilde proof fixtures self-contained --- .../fixtures/issue-16105-skeleton.json | 165 ++++++++++++++++++ .../scripts/fixtures/seed-issue-16105.py | 120 +++++++------ 2 files changed, 231 insertions(+), 54 deletions(-) create mode 100644 apps/server/scripts/fixtures/issue-16105-skeleton.json diff --git a/apps/server/scripts/fixtures/issue-16105-skeleton.json b/apps/server/scripts/fixtures/issue-16105-skeleton.json new file mode 100644 index 0000000000000..33b34792e4177 --- /dev/null +++ b/apps/server/scripts/fixtures/issue-16105-skeleton.json @@ -0,0 +1,165 @@ +{ + "thread": { + "createdBy": "user", + "creationSource": "web", + "id": "proof:id", + "projectId": "proof:projectId", + "title": "16105 synthetic markdown proof", + "providerInstanceId": "codex", + "modelSelection": { + "instanceId": "codex", + "model": "gpt-6-astra" + }, + "runtimeMode": "full-access", + "interactionMode": "default", + "branch": null, + "worktreePath": null, + "pullRequests": [], + "activeProviderThreadId": null, + "lineage": { + "parentThreadId": null, + "relationshipToParent": null, + "rootThreadId": "9480aec7-3adf-40ed-a5b8-1758521cd391" + }, + "forkedFrom": null, + "createdAt": "2026-10-21T12:00:00.000Z", + "updatedAt": "2026-10-21T12:00:00.000Z", + "archivedAt": null, + "settledOverride": null, + "settledAt": null, + "snoozedUntil": null, + "snoozedAt": null, + "lastVisitedAt": "2026-10-21T12:00:00.000Z", + "titleRegeneration": null, + "deletedAt": null + }, + "run": { + "id": "proof:id", + "threadId": "proof:threadId", + "ordinal": 1, + "providerInstanceId": "codex", + "modelSelection": { + "instanceId": "codex", + "model": "gpt-6-astra" + }, + "providerThreadId": null, + "userMessageId": "proof:userMessageId", + "rootNodeId": "proof:rootNodeId", + "activeAttemptId": null, + "status": "completed", + "queuePosition": null, + "requestedAt": "2026-10-21T12:00:00.000Z", + "startedAt": "2026-10-21T12:00:00.000Z", + "completedAt": "2026-10-21T12:00:00.000Z", + "checkpointId": null, + "contextHandoffId": null, + "workspacePreparation": { + "type": "root" + } + }, + "rootNode": { + "id": "proof:id", + "threadId": "proof:threadId", + "runId": "proof:runId", + "parentNodeId": null, + "rootNodeId": "proof:rootNodeId", + "kind": "root_turn", + "status": "completed", + "countsForRun": true, + "providerThreadId": null, + "providerTurnId": null, + "nativeItemRef": null, + "runtimeRequestId": null, + "checkpointScopeId": null, + "startedAt": "2026-10-21T12:00:00.000Z", + "completedAt": "2026-10-21T12:00:00.000Z" + }, + "assistantNode": { + "id": "proof:id", + "threadId": "proof:threadId", + "runId": "proof:runId", + "parentNodeId": "proof:parentNodeId", + "rootNodeId": "proof:rootNodeId", + "kind": "assistant_message", + "status": "completed", + "countsForRun": false, + "providerThreadId": null, + "providerTurnId": null, + "nativeItemRef": null, + "runtimeRequestId": null, + "checkpointScopeId": null, + "startedAt": "2026-10-21T12:00:00.000Z", + "completedAt": "2026-10-21T12:00:00.000Z" + }, + "userMessage": { + "createdBy": "user", + "creationSource": "web", + "id": "proof:id", + "threadId": "proof:threadId", + "runId": "proof:runId", + "nodeId": "proof:nodeId", + "role": "user", + "text": "", + "attachments": [], + "streaming": false, + "createdAt": "2026-10-21T12:00:00.000Z", + "updatedAt": "2026-10-21T12:00:00.000Z" + }, + "userItem": { + "id": "proof:id", + "threadId": "proof:threadId", + "runId": "proof:runId", + "nodeId": "proof:nodeId", + "providerThreadId": null, + "providerTurnId": null, + "nativeItemRef": null, + "parentItemId": null, + "ordinal": 1000001, + "status": "completed", + "title": null, + "startedAt": "2026-10-21T12:00:00.000Z", + "completedAt": "2026-10-21T12:00:00.000Z", + "updatedAt": "2026-10-21T12:00:00.000Z", + "createdBy": "user", + "creationSource": "web", + "type": "user_message", + "messageId": "proof:messageId", + "inputIntent": "turn_start", + "text": "", + "attachments": [] + }, + "assistantMessage": { + "createdBy": "agent", + "creationSource": "provider", + "id": "proof:id", + "threadId": "proof:threadId", + "runId": "proof:runId", + "nodeId": "proof:nodeId", + "role": "assistant", + "text": "", + "attachments": [], + "streaming": false, + "createdAt": "2026-10-21T12:00:00.000Z", + "updatedAt": "2026-10-21T12:00:00.000Z" + }, + "assistantItem": { + "id": "proof:id", + "threadId": "proof:threadId", + "runId": "proof:runId", + "nodeId": "proof:nodeId", + "providerThreadId": null, + "providerTurnId": null, + "nativeItemRef": null, + "parentItemId": null, + "ordinal": 1000003, + "status": "completed", + "title": null, + "startedAt": "2026-10-21T12:00:00.000Z", + "completedAt": "2026-10-21T12:00:00.000Z", + "updatedAt": "2026-10-21T12:00:00.000Z", + "type": "assistant_message", + "messageId": "proof:messageId", + "text": "", + "streaming": false + } +} diff --git a/apps/server/scripts/fixtures/seed-issue-16105.py b/apps/server/scripts/fixtures/seed-issue-16105.py index d5ecd97e0da94..168bfc45e916b 100644 --- a/apps/server/scripts/fixtures/seed-issue-16105.py +++ b/apps/server/scripts/fixtures/seed-issue-16105.py @@ -1,80 +1,92 @@ #!/usr/bin/env python3 -"""Seed stopped, isolated proof databases from a UI-created thread skeleton. +"""Rebuild synthetic markdown projections in stopped, migrated isolated DBs. Usage: python3 seed-issue-16105.py /absolute/worktree/.t3 [...] -Projection-only visual/search fixtures; not coherent orchestration event history. +No existing project/thread or event history is needed. These projection-only +visual/search fixtures must never be used for provider or orchestration tests. """ +import copy import json import pathlib import socket import sqlite3 import sys -fixture = json.loads(pathlib.Path(__file__).with_name('issue-16105.json').read_text()) +folder = pathlib.Path(__file__).parent +fixture = json.loads((folder / 'issue-16105.json').read_text()) +templates = json.loads((folder / 'issue-16105-skeleton.json').read_text()) for port in (14829, 15209): with socket.socket() as probe: if probe.connect_ex(('127.0.0.1', port)) == 0: raise SystemExit(f'Stop proof server on {port} before seeding') for argument in sys.argv[1:]: home = pathlib.Path(argument).resolve() - if home.name != '.t3' or home.parent.parent != pathlib.Path('/home/ubuntu/wt'): - raise SystemExit('Only isolated /home/ubuntu/wt//.t3 homes are allowed') + if home.name != '.t3' or home == pathlib.Path.home() / '.t3': + raise SystemExit('Only explicit isolated worktree .t3 homes are allowed') database = home / 'userdata/statev2.sqlite' + if not database.is_file(): + raise SystemExit('Initialize/migrate this isolated database first') connection = sqlite3.connect(database) - connection.row_factory = sqlite3.Row thread = fixture['threadId'] - messages = list(connection.execute('SELECT * FROM orchestration_v2_projection_messages WHERE thread_id=? ORDER BY created_at,message_id', (thread,))) - items = list(connection.execute('SELECT * FROM orchestration_v2_projection_turn_items WHERE thread_id=? ORDER BY ordinal', (thread,))) - positions = {json.loads(row['payload_json']).get('messageId'): row['ordinal'] for row in items} - messages.sort(key=lambda row: positions[row['message_id']]) - templates = {role: json.loads(next(row['payload_json'] for row in messages if row['role'] == role)) for role in ('user', 'assistant')} - item_templates = {role: json.loads(next(row['payload_json'] for row in items if row['type'] == role + '_message')) for role in ('user', 'assistant')} - assert len(messages) >= len(fixture['messages']), 'Need five real message skeletons' + project = '8129f6fb-0c4b-4e6b-9527-879d6b27b934' + stamp = fixture['timestamp'] backup = database.with_suffix('.before-16105-seed.sqlite') if not backup.exists(): with sqlite3.connect(backup) as target: connection.backup(target) - seeded = [] + rows = {} + def add(table, row): + rows.setdefault(table, []).append(row) + def projected(table, payload, **columns): + add(table, {**columns, 'payload_json': json.dumps(payload)}) + add('projection_projects', dict(project_id=project,title='16105 synthetic proof',workspace_root=str(home.parent),scripts_json='[]',created_at=stamp,updated_at=stamp,deleted_at=None)) + body = copy.deepcopy(templates['thread']) + body.update(id=thread,projectId=project) + projected('orchestration_v2_projection_threads',body,thread_id=thread,project_id=project,title=body['title'],default_provider='codex',provider_instance_id='codex',runtime_mode=body['runtimeMode'],interaction_mode=body['interactionMode'],active_provider_thread_id=None,created_at=stamp,updated_at=stamp,archived_at=None,deleted_at=None) + run_ordinal = 0 + for index, case in enumerate(fixture['messages']): + if case['role'] == 'user': + run_ordinal += 1 + run = f'run:proof-16105:{run_ordinal}' + root = f'node:proof-16105:{run_ordinal}:root' + message_id = f'message:proof-16105:{case["fixture"]}' + body = copy.deepcopy(templates['run']) + body.update(id=run,threadId=thread,ordinal=run_ordinal,userMessageId=message_id,rootNodeId=root) + projected('orchestration_v2_projection_runs',body,run_id=run,thread_id=thread,ordinal=run_ordinal,provider='codex',provider_instance_id='codex',provider_thread_id=None,status='completed',requested_at=stamp,completed_at=stamp) + body = copy.deepcopy(templates['rootNode']) + body.update(id=root,threadId=thread,runId=run,rootNodeId=root,parentNodeId=None) + projected('orchestration_v2_projection_nodes',body,node_id=root,thread_id=thread,run_id=run,parent_node_id=None,root_node_id=root,kind='root_turn',status='completed',provider_thread_id=None,provider_turn_id=None,runtime_request_id=None,checkpoint_scope_id=None,started_at=stamp,completed_at=stamp) + role = case['role'] + message_id = f'message:proof-16105:{case["fixture"]}' + node = root if role == 'user' else f'node:proof-16105:{case["fixture"]}' + if role == 'assistant': + body = copy.deepcopy(templates['assistantNode']) + body.update(id=node,threadId=thread,runId=run,parentNodeId=root,rootNodeId=root) + projected('orchestration_v2_projection_nodes',body,node_id=node,thread_id=thread,run_id=run,parent_node_id=root,root_node_id=root,kind='assistant_message',status='completed',provider_thread_id=None,provider_turn_id=None,runtime_request_id=None,checkpoint_scope_id=None,started_at=stamp,completed_at=stamp) + message = copy.deepcopy(templates[role+'Message']) + message.update(id=message_id,threadId=thread,runId=run,nodeId=node,text=case['text']) + item = copy.deepcopy(templates[role+'Item']) + item_id = f'turn-item:proof-16105:{case["fixture"]}' + ordinal = (index+1)*1000000 + item.update(id=item_id,threadId=thread,runId=run,nodeId=node,messageId=message_id,text=case['text'],ordinal=ordinal) + for key in ('context','scheduledTaskId'): + if key in case: + message[key] = case[key] + item[key] = case[key] + projected('orchestration_v2_projection_messages',message,message_id=message_id,thread_id=thread,run_id=run,node_id=node,role=role,streaming=0,created_at=stamp,updated_at=stamp) + projected('orchestration_v2_projection_turn_items',item,turn_item_id=item_id,thread_id=thread,run_id=run,node_id=node,provider_thread_id=None,provider_turn_id=None,parent_item_id=None,ordinal=ordinal,type=role+'_message',status='completed',updated_at=stamp) + add('orchestration_v2_turn_item_positions',dict(thread_id=thread,turn_item_id=item_id,ordinal=ordinal)) with connection: - keep_items = [] - keep_messages = [] - for source, case in zip(messages, fixture['messages']): - source_payload = json.loads(source['payload_json']) - old_item = next(row for row in items if json.loads(row['payload_json']).get('messageId') == source['message_id']) - old_payload = json.loads(old_item['payload_json']) - role = case['role'] - message = dict(templates[role]) - item = dict(item_templates[role]) - for key in ('id', 'threadId', 'runId', 'nodeId'): - message[key] = source_payload[key] - for key in ('id', 'threadId', 'runId', 'nodeId', 'providerThreadId', 'providerTurnId', 'nativeItemRef', 'parentItemId', 'ordinal'): - item[key] = old_payload[key] - item['messageId'] = message['id'] - for payload in (message, item): - for key in ('context', 'scheduledTaskId'): - payload.pop(key, None) - if key in case: - payload[key] = case[key] - payload['text'] = case['text'] - for key in ('createdAt', 'updatedAt', 'startedAt', 'completedAt'): - if key in payload: - payload[key] = fixture['timestamp'] - message['streaming'] = False - if role == 'assistant': - item['streaming'] = False - connection.execute('UPDATE orchestration_v2_projection_messages SET role=?,streaming=0,created_at=?,updated_at=?,payload_json=? WHERE message_id=?', (role, fixture['timestamp'], fixture['timestamp'], json.dumps(message), message['id'])) - connection.execute('UPDATE orchestration_v2_projection_turn_items SET type=?,updated_at=?,payload_json=? WHERE turn_item_id=?', (role+'_message', fixture['timestamp'], json.dumps(item), item['id'])) - keep_items.append(item['id']) - keep_messages.append(message['id']) - seeded.append({'fixture':case['fixture'], 'message':message, 'turnItem':item}) - for table, column, retained in [('orchestration_v2_projection_messages','message_id',keep_messages), ('orchestration_v2_projection_turn_items','turn_item_id',keep_items), ('orchestration_v2_turn_item_positions','turn_item_id',keep_items)]: - placeholders = ','.join('?' for _ in retained) - connection.execute(f'DELETE FROM {table} WHERE thread_id=? AND {column} NOT IN ({placeholders})', [thread, *retained]) - row = connection.execute('SELECT payload_json FROM orchestration_v2_projection_threads WHERE thread_id=?', (thread,)).fetchone() - payload = json.loads(row[0]) - payload['title'] = fixture['title'] - connection.execute('UPDATE orchestration_v2_projection_threads SET title=?,payload_json=? WHERE thread_id=?', (fixture['title'],json.dumps(payload),thread)) + tables = [r[0] for r in connection.execute("SELECT name FROM sqlite_schema WHERE type='table' AND (name LIKE 'orchestration_v2_projection_%' OR name='orchestration_v2_turn_item_positions')")] + for table in tables: + if 'thread_id' in [r[1] for r in connection.execute(f'PRAGMA table_info({table})')]: + connection.execute(f'DELETE FROM {table} WHERE thread_id=?',(thread,)) + for table, values in rows.items(): + for row in values: + keys = ','.join(row) + placeholders = ','.join('?' for _ in row) + connection.execute(f'INSERT OR REPLACE INTO {table} ({keys}) VALUES ({placeholders})',list(row.values())) manifest = database.with_name('issue-16105-seeded.json') - manifest.write_text(json.dumps({'fixture':fixture,'seeded':seeded},indent=2)+'\n') + manifest.write_text(json.dumps({'fixture':fixture,'rows':rows},indent=2)+'\n') connection.close() - print(json.dumps({'database':str(database),'manifest':str(manifest),'messages':len(seeded)})) + print(json.dumps({'database':str(database),'manifest':str(manifest),'messages':len(fixture['messages'])})) From 8ac56474a31b840c4b149d58730a0a34cf98e3df Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 21 Oct 2026 23:47:05 +0000 Subject: [PATCH 5/7] test(web): separate table from list in tilde fixture --- apps/server/scripts/fixtures/issue-16105.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/server/scripts/fixtures/issue-16105.json b/apps/server/scripts/fixtures/issue-16105.json index ecd091167963f..a277ed8e8545e 100644 --- a/apps/server/scripts/fixtures/issue-16105.json +++ b/apps/server/scripts/fixtures/issue-16105.json @@ -7,12 +7,12 @@ { "fixture": "F1", "role": "user", - "text": "Install ~/folder-a/skills/x, then read (~/folder-a/guide.md).\nKeep ~solo~ and ~~old~~ with **bold** and [guide](https://example.com).\n> Quote ~/quote/a then (~/quote/b).\n- List ~/list/a then (~/list/b).\n| A | B |\n| - | - |\n| ~/table/a | (~/table/b) |" + "text": "Install ~/folder-a/skills/x, then read (~/folder-a/guide.md).\nKeep ~solo~ and ~~old~~ with **bold** and [guide](https://example.com).\n> Quote ~/quote/a then (~/quote/b).\n- List ~/list/a then (~/list/b).\n\n| A | B |\n| - | - |\n| ~/table/a | (~/table/b) |" }, { "fixture": "F1A", "role": "assistant", - "text": "Install ~/folder-a/skills/x, then read (~/folder-a/guide.md).\nKeep ~solo~ and ~~old~~ with **bold** and [guide](https://example.com).\n> Quote ~/quote/a then (~/quote/b).\n- List ~/list/a then (~/list/b).\n| A | B |\n| - | - |\n| ~/table/a | (~/table/b) |" + "text": "Install ~/folder-a/skills/x, then read (~/folder-a/guide.md).\nKeep ~solo~ and ~~old~~ with **bold** and [guide](https://example.com).\n> Quote ~/quote/a then (~/quote/b).\n- List ~/list/a then (~/list/b).\n\n| A | B |\n| - | - |\n| ~/table/a | (~/table/b) |" }, { "fixture": "F2", From 27e08b5b7a0b49716f78a0ebff74c19b9009ffe6 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 11 Nov 2026 01:42:25 +0000 Subject: [PATCH 6/7] chore: drop issue-16105 evidence fixtures from the change AI-assisted; verified with the tests and tools listed above. --- .../fixtures/issue-16105-skeleton.json | 165 ------------------ apps/server/scripts/fixtures/issue-16105.json | 38 ---- .../scripts/fixtures/seed-issue-16105.py | 92 ---------- 3 files changed, 295 deletions(-) delete mode 100644 apps/server/scripts/fixtures/issue-16105-skeleton.json delete mode 100644 apps/server/scripts/fixtures/issue-16105.json delete mode 100644 apps/server/scripts/fixtures/seed-issue-16105.py diff --git a/apps/server/scripts/fixtures/issue-16105-skeleton.json b/apps/server/scripts/fixtures/issue-16105-skeleton.json deleted file mode 100644 index 33b34792e4177..0000000000000 --- a/apps/server/scripts/fixtures/issue-16105-skeleton.json +++ /dev/null @@ -1,165 +0,0 @@ -{ - "thread": { - "createdBy": "user", - "creationSource": "web", - "id": "proof:id", - "projectId": "proof:projectId", - "title": "16105 synthetic markdown proof", - "providerInstanceId": "codex", - "modelSelection": { - "instanceId": "codex", - "model": "gpt-6-astra" - }, - "runtimeMode": "full-access", - "interactionMode": "default", - "branch": null, - "worktreePath": null, - "pullRequests": [], - "activeProviderThreadId": null, - "lineage": { - "parentThreadId": null, - "relationshipToParent": null, - "rootThreadId": "9480aec7-3adf-40ed-a5b8-1758521cd391" - }, - "forkedFrom": null, - "createdAt": "2026-10-21T12:00:00.000Z", - "updatedAt": "2026-10-21T12:00:00.000Z", - "archivedAt": null, - "settledOverride": null, - "settledAt": null, - "snoozedUntil": null, - "snoozedAt": null, - "lastVisitedAt": "2026-10-21T12:00:00.000Z", - "titleRegeneration": null, - "deletedAt": null - }, - "run": { - "id": "proof:id", - "threadId": "proof:threadId", - "ordinal": 1, - "providerInstanceId": "codex", - "modelSelection": { - "instanceId": "codex", - "model": "gpt-6-astra" - }, - "providerThreadId": null, - "userMessageId": "proof:userMessageId", - "rootNodeId": "proof:rootNodeId", - "activeAttemptId": null, - "status": "completed", - "queuePosition": null, - "requestedAt": "2026-10-21T12:00:00.000Z", - "startedAt": "2026-10-21T12:00:00.000Z", - "completedAt": "2026-10-21T12:00:00.000Z", - "checkpointId": null, - "contextHandoffId": null, - "workspacePreparation": { - "type": "root" - } - }, - "rootNode": { - "id": "proof:id", - "threadId": "proof:threadId", - "runId": "proof:runId", - "parentNodeId": null, - "rootNodeId": "proof:rootNodeId", - "kind": "root_turn", - "status": "completed", - "countsForRun": true, - "providerThreadId": null, - "providerTurnId": null, - "nativeItemRef": null, - "runtimeRequestId": null, - "checkpointScopeId": null, - "startedAt": "2026-10-21T12:00:00.000Z", - "completedAt": "2026-10-21T12:00:00.000Z" - }, - "assistantNode": { - "id": "proof:id", - "threadId": "proof:threadId", - "runId": "proof:runId", - "parentNodeId": "proof:parentNodeId", - "rootNodeId": "proof:rootNodeId", - "kind": "assistant_message", - "status": "completed", - "countsForRun": false, - "providerThreadId": null, - "providerTurnId": null, - "nativeItemRef": null, - "runtimeRequestId": null, - "checkpointScopeId": null, - "startedAt": "2026-10-21T12:00:00.000Z", - "completedAt": "2026-10-21T12:00:00.000Z" - }, - "userMessage": { - "createdBy": "user", - "creationSource": "web", - "id": "proof:id", - "threadId": "proof:threadId", - "runId": "proof:runId", - "nodeId": "proof:nodeId", - "role": "user", - "text": "", - "attachments": [], - "streaming": false, - "createdAt": "2026-10-21T12:00:00.000Z", - "updatedAt": "2026-10-21T12:00:00.000Z" - }, - "userItem": { - "id": "proof:id", - "threadId": "proof:threadId", - "runId": "proof:runId", - "nodeId": "proof:nodeId", - "providerThreadId": null, - "providerTurnId": null, - "nativeItemRef": null, - "parentItemId": null, - "ordinal": 1000001, - "status": "completed", - "title": null, - "startedAt": "2026-10-21T12:00:00.000Z", - "completedAt": "2026-10-21T12:00:00.000Z", - "updatedAt": "2026-10-21T12:00:00.000Z", - "createdBy": "user", - "creationSource": "web", - "type": "user_message", - "messageId": "proof:messageId", - "inputIntent": "turn_start", - "text": "", - "attachments": [] - }, - "assistantMessage": { - "createdBy": "agent", - "creationSource": "provider", - "id": "proof:id", - "threadId": "proof:threadId", - "runId": "proof:runId", - "nodeId": "proof:nodeId", - "role": "assistant", - "text": "", - "attachments": [], - "streaming": false, - "createdAt": "2026-10-21T12:00:00.000Z", - "updatedAt": "2026-10-21T12:00:00.000Z" - }, - "assistantItem": { - "id": "proof:id", - "threadId": "proof:threadId", - "runId": "proof:runId", - "nodeId": "proof:nodeId", - "providerThreadId": null, - "providerTurnId": null, - "nativeItemRef": null, - "parentItemId": null, - "ordinal": 1000003, - "status": "completed", - "title": null, - "startedAt": "2026-10-21T12:00:00.000Z", - "completedAt": "2026-10-21T12:00:00.000Z", - "updatedAt": "2026-10-21T12:00:00.000Z", - "type": "assistant_message", - "messageId": "proof:messageId", - "text": "", - "streaming": false - } -} diff --git a/apps/server/scripts/fixtures/issue-16105.json b/apps/server/scripts/fixtures/issue-16105.json deleted file mode 100644 index a277ed8e8545e..0000000000000 --- a/apps/server/scripts/fixtures/issue-16105.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "threadId": "9480aec7-3adf-40ed-a5b8-1758521cd391", - "timestamp": "2026-10-21T12:00:00.000Z", - "title": "16105 synthetic markdown proof", - "contextVisual": "UNVERIFIED: no generated terminal record available; empty-record context tests extraction only", - "messages": [ - { - "fixture": "F1", - "role": "user", - "text": "Install ~/folder-a/skills/x, then read (~/folder-a/guide.md).\nKeep ~solo~ and ~~old~~ with **bold** and [guide](https://example.com).\n> Quote ~/quote/a then (~/quote/b).\n- List ~/list/a then (~/list/b).\n\n| A | B |\n| - | - |\n| ~/table/a | (~/table/b) |" - }, - { - "fixture": "F1A", - "role": "assistant", - "text": "Install ~/folder-a/skills/x, then read (~/folder-a/guide.md).\nKeep ~solo~ and ~~old~~ with **bold** and [guide](https://example.com).\n> Quote ~/quote/a then (~/quote/b).\n- List ~/list/a then (~/list/b).\n\n| A | B |\n| - | - |\n| ~/table/a | (~/table/b) |" - }, - { - "fixture": "F2", - "role": "user", - "text": "Inline `~/inline/a` and `~/inline/b`; literal html.\n```text\n~/backtick/a then (~/backtick/b).\n```\n~~~text\n~/tildefence/a then (~/tildefence/b).\n~~~\n\nTail ~/hidden/a then (~/hidden/b).\n[terminal label](t3-context://v1/terminal/terminal_1)", - "context": { - "version": 1, - "records": [] - } - }, - { - "fixture": "F3", - "role": "user", - "text": "Automation ~/auto/a then (~/auto/b). Keep ~~old~~.", - "scheduledTaskId": "task:proof-16105" - }, - { - "fixture": "F4", - "role": "user", - "text": "Legacy ~/legacy/a then (~/legacy/b).\n\n\n- Terminal 1 line 12:\n 12 | hidden output\n" - } - ] -} diff --git a/apps/server/scripts/fixtures/seed-issue-16105.py b/apps/server/scripts/fixtures/seed-issue-16105.py deleted file mode 100644 index 168bfc45e916b..0000000000000 --- a/apps/server/scripts/fixtures/seed-issue-16105.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python3 -"""Rebuild synthetic markdown projections in stopped, migrated isolated DBs. - -Usage: python3 seed-issue-16105.py /absolute/worktree/.t3 [...] -No existing project/thread or event history is needed. These projection-only -visual/search fixtures must never be used for provider or orchestration tests. -""" -import copy -import json -import pathlib -import socket -import sqlite3 -import sys - -folder = pathlib.Path(__file__).parent -fixture = json.loads((folder / 'issue-16105.json').read_text()) -templates = json.loads((folder / 'issue-16105-skeleton.json').read_text()) -for port in (14829, 15209): - with socket.socket() as probe: - if probe.connect_ex(('127.0.0.1', port)) == 0: - raise SystemExit(f'Stop proof server on {port} before seeding') -for argument in sys.argv[1:]: - home = pathlib.Path(argument).resolve() - if home.name != '.t3' or home == pathlib.Path.home() / '.t3': - raise SystemExit('Only explicit isolated worktree .t3 homes are allowed') - database = home / 'userdata/statev2.sqlite' - if not database.is_file(): - raise SystemExit('Initialize/migrate this isolated database first') - connection = sqlite3.connect(database) - thread = fixture['threadId'] - project = '8129f6fb-0c4b-4e6b-9527-879d6b27b934' - stamp = fixture['timestamp'] - backup = database.with_suffix('.before-16105-seed.sqlite') - if not backup.exists(): - with sqlite3.connect(backup) as target: - connection.backup(target) - rows = {} - def add(table, row): - rows.setdefault(table, []).append(row) - def projected(table, payload, **columns): - add(table, {**columns, 'payload_json': json.dumps(payload)}) - add('projection_projects', dict(project_id=project,title='16105 synthetic proof',workspace_root=str(home.parent),scripts_json='[]',created_at=stamp,updated_at=stamp,deleted_at=None)) - body = copy.deepcopy(templates['thread']) - body.update(id=thread,projectId=project) - projected('orchestration_v2_projection_threads',body,thread_id=thread,project_id=project,title=body['title'],default_provider='codex',provider_instance_id='codex',runtime_mode=body['runtimeMode'],interaction_mode=body['interactionMode'],active_provider_thread_id=None,created_at=stamp,updated_at=stamp,archived_at=None,deleted_at=None) - run_ordinal = 0 - for index, case in enumerate(fixture['messages']): - if case['role'] == 'user': - run_ordinal += 1 - run = f'run:proof-16105:{run_ordinal}' - root = f'node:proof-16105:{run_ordinal}:root' - message_id = f'message:proof-16105:{case["fixture"]}' - body = copy.deepcopy(templates['run']) - body.update(id=run,threadId=thread,ordinal=run_ordinal,userMessageId=message_id,rootNodeId=root) - projected('orchestration_v2_projection_runs',body,run_id=run,thread_id=thread,ordinal=run_ordinal,provider='codex',provider_instance_id='codex',provider_thread_id=None,status='completed',requested_at=stamp,completed_at=stamp) - body = copy.deepcopy(templates['rootNode']) - body.update(id=root,threadId=thread,runId=run,rootNodeId=root,parentNodeId=None) - projected('orchestration_v2_projection_nodes',body,node_id=root,thread_id=thread,run_id=run,parent_node_id=None,root_node_id=root,kind='root_turn',status='completed',provider_thread_id=None,provider_turn_id=None,runtime_request_id=None,checkpoint_scope_id=None,started_at=stamp,completed_at=stamp) - role = case['role'] - message_id = f'message:proof-16105:{case["fixture"]}' - node = root if role == 'user' else f'node:proof-16105:{case["fixture"]}' - if role == 'assistant': - body = copy.deepcopy(templates['assistantNode']) - body.update(id=node,threadId=thread,runId=run,parentNodeId=root,rootNodeId=root) - projected('orchestration_v2_projection_nodes',body,node_id=node,thread_id=thread,run_id=run,parent_node_id=root,root_node_id=root,kind='assistant_message',status='completed',provider_thread_id=None,provider_turn_id=None,runtime_request_id=None,checkpoint_scope_id=None,started_at=stamp,completed_at=stamp) - message = copy.deepcopy(templates[role+'Message']) - message.update(id=message_id,threadId=thread,runId=run,nodeId=node,text=case['text']) - item = copy.deepcopy(templates[role+'Item']) - item_id = f'turn-item:proof-16105:{case["fixture"]}' - ordinal = (index+1)*1000000 - item.update(id=item_id,threadId=thread,runId=run,nodeId=node,messageId=message_id,text=case['text'],ordinal=ordinal) - for key in ('context','scheduledTaskId'): - if key in case: - message[key] = case[key] - item[key] = case[key] - projected('orchestration_v2_projection_messages',message,message_id=message_id,thread_id=thread,run_id=run,node_id=node,role=role,streaming=0,created_at=stamp,updated_at=stamp) - projected('orchestration_v2_projection_turn_items',item,turn_item_id=item_id,thread_id=thread,run_id=run,node_id=node,provider_thread_id=None,provider_turn_id=None,parent_item_id=None,ordinal=ordinal,type=role+'_message',status='completed',updated_at=stamp) - add('orchestration_v2_turn_item_positions',dict(thread_id=thread,turn_item_id=item_id,ordinal=ordinal)) - with connection: - tables = [r[0] for r in connection.execute("SELECT name FROM sqlite_schema WHERE type='table' AND (name LIKE 'orchestration_v2_projection_%' OR name='orchestration_v2_turn_item_positions')")] - for table in tables: - if 'thread_id' in [r[1] for r in connection.execute(f'PRAGMA table_info({table})')]: - connection.execute(f'DELETE FROM {table} WHERE thread_id=?',(thread,)) - for table, values in rows.items(): - for row in values: - keys = ','.join(row) - placeholders = ','.join('?' for _ in row) - connection.execute(f'INSERT OR REPLACE INTO {table} ({keys}) VALUES ({placeholders})',list(row.values())) - manifest = database.with_name('issue-16105-seeded.json') - manifest.write_text(json.dumps({'fixture':fixture,'rows':rows},indent=2)+'\n') - connection.close() - print(json.dumps({'database':str(database),'manifest':str(manifest),'messages':len(fixture['messages'])})) From 79dae2dbac9953bad2e9f1332bcb65ddb83d1c73 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 11 Nov 2026 01:45:38 +0000 Subject: [PATCH 7/7] style(web): format user message remark plugins AI-assisted; verified with the tests and tools listed above. --- apps/web/src/components/chat/TimelineUserMessageRow.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/chat/TimelineUserMessageRow.tsx b/apps/web/src/components/chat/TimelineUserMessageRow.tsx index ad788a463c492..9e25537bc5cd5 100644 --- a/apps/web/src/components/chat/TimelineUserMessageRow.tsx +++ b/apps/web/src/components/chat/TimelineUserMessageRow.tsx @@ -1188,7 +1188,10 @@ const CollapsibleUserMessageBody = memo(function CollapsibleUserMessageBody(prop ); }); -const USER_MESSAGE_REMARK_PLUGINS = [...CHAT_USER_MARKDOWN_REMARK_PLUGINS, remarkEnableOrderedListNumbers]; +const USER_MESSAGE_REMARK_PLUGINS = [ + ...CHAT_USER_MARKDOWN_REMARK_PLUGINS, + remarkEnableOrderedListNumbers, +]; const UserMessageBody = memo(function UserMessageBody(props: { text: string;