Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3c2af29
refactor(desktop): mount one chat shell for every account
Git-on-my-level Sep 2, 2026
176608a
feat(desktop): render every content block as an interactable component
Git-on-my-level Sep 2, 2026
3572ee2
fix(desktop): every proactive card says what it is
Git-on-my-level Sep 2, 2026
4e2dd73
test(desktop): pin one shell and six live blocks, and retire the seco…
Git-on-my-level Sep 2, 2026
8d458b8
fix(desktop): the notch and the main window share one navigation owner
Git-on-my-level Sep 2, 2026
f2603c5
refactor(desktop): delete the intelligence store nothing renders any …
Git-on-my-level Sep 2, 2026
55e75e5
fix(desktop): an explicit settings section still wins over the help d…
Git-on-my-level Sep 2, 2026
7f4b440
fix(desktop): let a settled chat answer be selected, copied and seen …
Git-on-my-level Sep 2, 2026
1d03f93
fix(desktop): stop charging the transcript twice for the metadata band
Git-on-my-level Sep 2, 2026
7e88013
feat(app): decode chat content blocks into typed models
Git-on-my-level Sep 2, 2026
74776c8
feat(app): add l10n keys for chat content-block components
Git-on-my-level Sep 2, 2026
78b9f08
feat(app): render chat content blocks as interactable components
Git-on-my-level Sep 2, 2026
c762507
docs: record the presentation-cohort-drops-journaled-content failure …
Git-on-my-level Sep 2, 2026
b9c0aaa
fix(desktop): give the reader a selectable copy instead of a selectab…
Git-on-my-level Sep 2, 2026
a4c42e1
test(desktop): drop the deleted shell files from the static guards
Git-on-my-level Sep 2, 2026
803c4dd
test(desktop): repoint ptt-lifecycle covers at ChatToolExecutor
Git-on-my-level Sep 2, 2026
91a2ae6
Merge origin/main into fix/one-chat-shell-ergonomics
Git-on-my-level Sep 2, 2026
d06a603
fix(desktop): a reply the reader watched arrive stays whole when it s…
Git-on-my-level Sep 2, 2026
172947d
fix(desktop): the assistant reads the same task list the Tasks page s…
Git-on-my-level Sep 2, 2026
65fcc70
fix(desktop): the transcript's own follow-scroll is not the reader ta…
Git-on-my-level Sep 2, 2026
c013626
test(desktop): the tasks flow checks the lanes the assistant answers …
Git-on-my-level Sep 2, 2026
3d71cfb
fix(desktop): the components the agent renders reach the transcript
Git-on-my-level Sep 3, 2026
3da6977
fix(desktop): ticking a task card shows it done, not gone
Git-on-my-level Sep 3, 2026
99436fa
fix(chat): components replace the writing instead of doubling it
Git-on-my-level Sep 3, 2026
09662d6
feat(desktop): selection lives in the words, not in a box beside them
Git-on-my-level Sep 3, 2026
7f77484
fix(chat): a rendered card is the citation, and it survives the next …
Git-on-my-level Sep 3, 2026
141b83a
fix(desktop): every citation marker the transcript writes is selectab…
Git-on-my-level Sep 3, 2026
1412afe
chore(desktop): the cohesive chat flow covers the selection renderer …
Git-on-my-level Sep 3, 2026
f8d9e54
test(ci): the selection boundary fixtures cover the AppKit surface too
Git-on-my-level Sep 3, 2026
eb36034
chore(agent): the component cap is not a licence to write the list ou…
Git-on-my-level Sep 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/checks-manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,16 @@ checks:
triggers: ["desktop/macos/Desktop/Sources/**/*.swift", "desktop/macos/scripts/check-grdb-insert-idiom.py", ".github/checks-manifest.yaml"]
lanes: ["local", "ci"]
reason: "#11204: a mutating didInsert never witnesses PersistableRecord's non-mutating insert, so a direct record.insert(db) drops the rowid silently onto an optional field; two shipped instances went undetected until unrelated work tripped over them"
- id: desktop-single-chat-shell-self-test
command: ["python3", "desktop/macos/scripts/check-single-chat-shell.py", "--self-test"]
triggers: ["desktop/macos/scripts/check-single-chat-shell.py", ".github/checks-manifest.yaml"]
lanes: ["local", "ci"]
reason: "the tripwire must keep failing on each shape it was written for, and keep passing on prose that merely names one"
- id: desktop-single-chat-shell
command: ["python3", "desktop/macos/scripts/check-single-chat-shell.py"]
triggers: ["desktop/macos/Desktop/Sources/**/*.swift", "desktop/macos/scripts/check-single-chat-shell.py", ".github/checks-manifest.yaml"]
lanes: ["local", "ci"]
reason: "#12598: the app mounted one of two shells behind a preference, and six content-block kinds rendered as controls on one and as nothing on the other; both grow back one symbol at a time"
- id: brand-ui-ratchet-tests
command: ["python3", ".github/scripts/test_check_brand_ui.py"]
triggers: [".github/scripts/check_brand_ui.py", ".github/scripts/test_check_brand_ui.py", ".github/checks-manifest.yaml"]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"schema_version": 1,
"id": "FC-presentation-cohort-drops-journaled-content",
"violated_contract": "A client-side presentation cohort must never decide whether durable, journaled content is drawn. The macOS chat journaled taskCard, goalLink, captureLink, conversationLink, memoryLink and questionCard blocks for every account, but only the shell sampled into the chat-first cohort was handed a ChatFirstRichBlockContext; every other host (legacy shell, task chat panel, floating bar, notch) decoded the same blocks and rendered EmptyView. A task created during onboarding was therefore in the transcript and invisible, and the same transcript looked different per shell, per cohort, and per surface. The defect reads as 'the task was never created', not as a rendering gate, because the journal is correct.",
"canonical_prevention": "One renderer, no optional rendering context: every chat surface receives a non-optional rich-block context, and a behavioural test journals one message carrying every block type and asserts the shared renderer yields the interactable view for each. A static tripwire forbids the retired cohort/legacy symbols and any 'context == nil' branch from returning. Capability gates may disable an action on a card; they may not decide whether the card exists.",
"canonical_prevention_artifact": [
"desktop/macos/Desktop/Sources/MainWindow/ChatFirst/Blocks/ChatFirstRichBlockGroupView.swift",
"desktop/macos/Desktop/Tests/OneChatShellRichBlockTests.swift",
"desktop/macos/scripts/check-single-chat-shell.py",
"app/test/widgets/chat_content_blocks_test.dart"
],
"scope_hints": [
"desktop/macos/Desktop/Sources/MainWindow/Components/ChatBubble.swift",
"desktop/macos/Desktop/Sources/MainWindow/ChatFirst/**",
"app/lib/pages/chat/widgets/**"
],
"status": "open",
"evidence_prs": []
}
25 changes: 21 additions & 4 deletions .github/scripts/check_chat_selection_boundary.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@

SwiftUI has no type-level API that prevents an ancestor or message renderer
from installing SelectionOverlay. This deliberately narrow source tripwire
therefore protects the three authoritative live-transcript files. Behavioral
resize coverage remains in ChatTimelineContinuityTests.
therefore protects the authoritative live-transcript files. Behavioral resize
coverage remains in ChatTimelineContinuityTests.

The bar is on `SelectionOverlay`, not on selecting. The transcript now hosts
selection through `ChatSelectableProse` — one `NSTextView` per prose block,
which *is* its own selection and installs no per-`Text` overlay for a parent
rebuild to thrash. That file is protected here too, so the AppKit path can
never quietly acquire the SwiftUI one.
"""

from __future__ import annotations
Expand All @@ -25,13 +31,15 @@
"desktop/macos/Desktop/Sources/MainWindow/Components/ChatBubble.swift",
"desktop/macos/Desktop/Sources/MainWindow/Components/ChatMessagesView.swift",
"desktop/macos/Desktop/Sources/MainWindow/Components/OmiMarkdown.swift",
"desktop/macos/Desktop/Sources/MainWindow/Components/ChatSelectableProse.swift",
)
MARKDOWN_FILE = LIVE_TRANSCRIPT_FILES[2]
SELECTION_FILE = LIVE_TRANSCRIPT_FILES[3]

FORBIDDEN_PATTERNS = {
".textSelection(.enabled)": (
"live chat must not install SwiftUI SelectionOverlay; use the existing copy actions "
"or a separate non-live reading surface"
"live chat must not install SwiftUI SelectionOverlay; selection belongs to "
"ChatSelectableProse, whose NSTextView owns it without one"
),
"textSelectionEnabled": (
"OmiMarkdown must not expose a native-selection escape hatch"
Expand Down Expand Up @@ -59,6 +67,15 @@ def check_sources(sources: Mapping[str, str]) -> list[str]:
f"{MARKDOWN_FILE}: OmiMarkdown must explicitly disable inherited native text selection"
)

# The sanctioned remedy has to stay AppKit. An NSTextView owning its own
# selection is the whole reason selection is allowed back into the
# transcript; a SwiftUI Text here would reopen the failure class.
selection_source = sources.get(SELECTION_FILE)
if selection_source is not None and "NSTextView" not in selection_source:
failures.append(
f"{SELECTION_FILE}: transcript selection must be hosted by an NSTextView"
)

return failures


Expand Down
18 changes: 18 additions & 0 deletions .github/scripts/test_check_chat_selection_boundary.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ def clean_sources() -> dict[str, str]:
CHECKER.MARKDOWN_FILE: (
"struct OmiMarkdown { var body: some View { Text(text).textSelection(.disabled) } }\n"
),
CHECKER.SELECTION_FILE: (
"struct ChatSelectableProseText: NSViewRepresentable {\n"
" func makeNSView(context: Context) -> NSTextView { ChatProseTextView() }\n"
"}\n"
),
}


Expand Down Expand Up @@ -61,6 +66,19 @@ def test_requires_explicit_disabled_boundary(self) -> None:

self.assertTrue(any("explicitly disable" in failure for failure in failures))

def test_requires_the_selection_surface_to_stay_appkit(self) -> None:
"""The remedy is an NSTextView owning its own selection. A SwiftUI
rewrite of this file would put SelectionOverlay back in the transcript
under a name the pattern check cannot see."""
sources = clean_sources()
sources[CHECKER.SELECTION_FILE] = (
"struct ChatSelectableProseText: View { var body: some View { Text(text) } }\n"
)

failures = CHECKER.check_sources(sources)

self.assertTrue(any("NSTextView" in failure for failure in failures))

def test_rejects_missing_protected_source(self) -> None:
sources = clean_sources()
missing = CHECKER.LIVE_TRANSCRIPT_FILES[1]
Expand Down
Loading