Connect original sources, board previews and personal planning across experiences - #2866
Conversation
…omparisons # Conflicts: # docs/IMPLEMENTATION_MASTERPLAN.md
…text-base-refresh
…panion-continuity
… into codex/2808-comparison-compat
…omparisons # Conflicts: # docs/STATUS.md # docs/product/WORKSPACE_OVERHAUL_VALIDATION.md
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7886fc93cc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| var answer = await OwnedAsync(userId, id, ct); | ||
| if (answer.ConfirmedMemoryId.HasValue) return await MapAsync(answer, ct); | ||
| if (answer.Revision != dto.ExpectedRevision || answer.RepresentationId != dto.RepresentationId) throw Conflict(); |
There was a problem hiding this comment.
Reject mismatched retries after audio confirmation
When another tab has already confirmed the recording, this early return accepts any subsequent confirm payload without validating its revision, representation, deck revision, or status. For example, a stale tab can request assumption after the first tab stored statement and receive 200 even though that status was not persisted; preserve lost-response idempotency only when the request matches the completed confirmation and return a conflict otherwise.
AGENTS.md reference: backend/AGENTS.md:L22-L25
Useful? React with 👍 / 👎.
| // The send already succeeded and its messages are retained locally. A | ||
| // failed reconciliation must not hold continuation behind read retries. | ||
| const result = await chatApi.getSession(sessionId, { skipRetry: true }) | ||
| if (isDisposed || requestedSessionId !== sessionId || selectedSession.value?.id !== sessionId) return | ||
| if (!isCurrent()) return |
There was a problem hiding this comment.
Put a timeout on the post-send receipt refresh
When this GET remains pending because the API or connection stalls, chatApi.getSession forwards only skipRetry and the shared Axios client has no request timeout. Since the send path awaits this refresh while refreshingReceipt disables every send control, the successfully sent conversation can remain blocked indefinitely without ever reaching the new retry/error state; give this reconciliation read a bounded timeout or cancellation path.
Useful? React with 👍 / 👎.
| <router-link | ||
| v-if="proposal.boardId && !props.readOnly && ['PendingReview', 'Approved'].includes(normalizeProposalStatus(proposal.status))" | ||
| class="td-review-card__links-dropdown-item" | ||
| role="menuitem" | ||
| :to="{ path: `/workspace/boards/${proposal.boardId}`, query: { proposalId: proposal.id } }" |
There was a problem hiding this comment.
Expose board preview for non-Queue proposals
When Classic/Legacy Review displays a Chat or Manual/MCP proposal, ReviewProposalCard.hasProvenanceContext is false because it recognizes only Queue capture references. This link is nested inside the surrounding v-if="hasProvenance" block, so those pending or approved board proposals never receive the new Preview on board action, even though Paper Review exposes it independently; place the preview action outside the capture-provenance-only disclosure.
Useful? React with 👍 / 👎.
|
|
||
| | Domain | Entry points | Invariants (load-bearing) | Verify | | ||
| | --- | --- | --- | --- | | ||
| | Private audio answers | `Api/Controllers/ThinkingAudioController.cs` → `Application/Services/ThinkingAudioService.cs`; `CaptureIntakeService.StageAudioAnswerAsync` is the canonical capture writer; `Infrastructure/Storage/SqliteBlobStore.cs`, `Repositories/EfManualRepresentationStore.cs`, `Repositories/SourcePortabilityStore.cs`; frontend `components/thinking/ThinkingAudioAnswer.vue` + `AudioAnswerRecorder.vue`; owner library `components/workspace/OriginalAudioLibrary.vue` from Memory, API `library` list/detail/original | Audio alone is untranscribed/unanswered; explicit human writing and confirmation are separate immutable representations. Owner-scoped bytes and current board access; read-only library also permits archived boards with current permission and owner-linked deleted-board originals; upload ID/hash retry; question/revision conflicts; quotas before bounded reads; caller transaction; account erasure deletes representation dependants before source assets. No queued/remote transcription. Legacy representation backfill is separate. Avoid generated migration designers except for schema work; product/status sync: `docs/product/WORKSPACE_OVERHAUL.md` + validation ledger and STATUS/MASTERPLAN | API `ThinkingAudioApiTests`, `SqliteBlobStoreTests`, `ManualRepresentationStoreTests`, `DataPortabilityApiTests`; frontend `ThinkingAudioAnswer.spec.ts`, `AudioAnswerRecorder.spec.ts`, `OriginalAudioLibrary.spec.ts`, `ThinkingQuestionAnswer.spec.ts`, `ThinkingWorkspaceContinuity.spec.ts`; Chromium `tests/e2e/thinking-audio.spec.ts` with an isolated `TASKDECK_E2E_DB`; full backend/frontend per CLAUDE before PR | |
There was a problem hiding this comment.
Reconcile the Context Fabric entries with the implementations
This new seam row declares SqliteBlobStore, EfManualRepresentationStore, and SourcePortabilityStore as the production entry points, but the existing Context Fabric row at line 47 and its linked architecture map still say IBlobStore/IRepresentationStore are contract-only with no implementation registered. Because this index is the repository's required first-hop seam map, the contradictory entries can send subsequent work away from the newly shipped persistence path; update the older entries as part of this implementation change.
AGENTS.md reference: AGENTS.md:L13-L16
Useful? React with 👍 / 👎.
|
Required CI34434084906 passed at7886fc93; before merge, main had advanced through the separate CI-adapter PR2864 to c6a97b7. Reconciled that base to d0e08d5. Backend/frontend/deployment product inputs remain unchanged; new continuation tests and doclinks pass. The new exact-head gate is running; no merge is claimed. New hosted comments are classified once as MEDIUM and retained under #2808: 3975276160 (reject mismatched retries of an already-confirmed audio answer), 3975276166 (bound post-send receipt reconciliation), 3975276175 (show preview for non-Queue proposals in Legacy Review), and 3975276181 (reconcile older Context Fabric interface-map entries with concrete source stores). They do not authorize board writes, overwrite originals or change access boundaries, and will be handled in focused follow-through rather than restarting this reviewed application diff. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d0e08d570b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!live) return | ||
| if (playbackUrl.value) URL.revokeObjectURL(playbackUrl.value) | ||
| playbackUrl.value = URL.createObjectURL(blob) |
There was a problem hiding this comment.
Discard playback responses after recording changes
When playback starts and the shared question is revised before the download finishes, the revision reload can clear or replace saved, but this response is still accepted because it checks only live, not the requested recording ID or generation. If the revised question already has—or subsequently receives—a recording, the stale blob is rendered and downloaded under the new receipt and filename; capture the request generation/recording ID and discard or revoke responses after the receipt changes.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2ee779696c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| received += count; | ||
| hash.AppendData(buffer, 0, count); | ||
| var chunk = buffer.AsSpan(0, count).ToArray(); | ||
| await db.Database.ExecuteSqlInterpolatedAsync($"INSERT INTO StoredBlobChunks (BlobId, Ordinal, Content) VALUES ({blob.Id}, {ordinal}, {chunk})", cancellationToken); |
There was a problem hiding this comment.
Coalesce short reads before storing blob chunks
When an authenticated client slowly fragments an otherwise valid upload, Stream.ReadAsync may return only a few bytes at a time, and this statement inserts a separate SQLite row for every fragment. A 2 MiB request can therefore create an enormous number of StoredBlobChunks rows and monopolize the single SQLite writer while remaining within every byte quota; accumulate reads into full 65,536-byte chunks, except for the final chunk, before inserting.
Useful? React with 👍 / 👎.
| column: x => x.ConfirmedMemoryId, | ||
| principalTable: "WorkspaceMemories", | ||
| principalColumn: "Id", | ||
| onDelete: ReferentialAction.SetNull); |
There was a problem hiding this comment.
Preserve confirmation after deleting its board
When a board containing a confirmed audio answer is deleted, its WorkspaceMemory is cascade-deleted while the retained audio answer's ConfirmedMemoryId is set to null. That field is the only persisted confirmation flag used by the library, so the retained recording is subsequently presented as “Written version, unconfirmed” even though its verified representation remains and the deleted board prevents reconfirmation; retain confirmation independently or derive it from the verified representation.
Useful? React with 👍 / 👎.
| if (kind === 'card' && ['create', 'move'].includes(operation.actionType.toLowerCase()) && typeof parameters.columnId === 'string') { | ||
| const destination = `column:${parameters.columnId.toLowerCase()}` | ||
| if (existing.has(destination)) markers[destination] = 'Proposed change' |
There was a problem hiding this comment.
Mark both columns for moved cards
When a proposal moves an existing card, the overlay marks the card and destination column but not the card's current source column, even though that column's count and ordering are also affected. In the Paper board, collapsing the source column hides the card DOM, leaving no visible marker anywhere on that affected lane; resolve the moved card from props.cards and mark its current columnId as well.
Useful? React with 👍 / 👎.
|
Checkpoint: main remains688e4a08c. PR2866 HEAD2ee779696 has no failed required jobs; final Windows API and downstream E2E remain pending. Bounded base interaction review CLEAN and261localCIcontracts passed. No merge yet. The MEDIUM follow-ups3975276166 (bounded receipt read),3975276175 (Legacy Chat/Manual preview) and3975276181 (source interface map) are implemented in ready PR2886, with local proving evidence and independent review. 3975276160 (exact confirmation retries) has10API+isolatedmigration passing proof and is in full qualification. New3975407612 is confirmed MEDIUM: an in-flight original could be attached to a replaced receipt. A scoped follow-up now discards/revokes stale playback and has16component+real stalled-response browser proof; full frontend remains running. These continuations do not change this PR head or imply the whole2808 scope is complete. |
|
Confirmed HIGH3975612377: SqliteBlobStore previously persisted every short stream read as a separate row, allowing valid fragmented input to amplify SQLite writes within byte quotas. The scoped fix coalesces reads to the existing64KiB buffer, preserving a final short tail, bounded memory, declared-size checks, hash and savepoint rollback. New1-byte/97-byte cases proveexact full-chunk counts; +/-declared-size cases prove complete rollback.24blob/audio/exportAPItests pass. A bounded independent fix review is running; this head will not merge before the repair and exact-head gate pass. MEDIUM3975612383 (confirmation status after board deletion) and3975612391 (source-column marker for moved cards) are accepted follow-ups under2808. They do not imply board mutation or loss of retained audio bytes. Existing confirmation fingerprint work is PR2888; playback recovery is PR2887. No broad audit or unrelated changes are added to this repair. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 320acd7edd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (saved.ByteSize != dto.ByteSize || saved.MediaType != mediaType || saved.FileName != dto.FileName | ||
| || saved.ContentHash != await HashUploadAsync(content, dto.ByteSize, ct)) throw Conflict(); |
There was a problem hiding this comment.
Normalize filenames before checking upload retries
When an upload uses a valid filename with surrounding whitespace, such as " voice.wav", the first request succeeds because SourceAsset trims the stored OriginalName, but an identical retry with the same UploadId and body compares that normalized receipt to the raw filename and returns 409. This breaks lost-response idempotency for filenames permitted by the current validation; normalize or reject the filename before both persistence and retry comparison.
Useful? React with 👍 / 👎.
|
Hosted comment3975825509 is a confirmed MEDIUM upload-retry defect: the stored filename was trimmed while retry comparison used the raw name. Commit66b082a8d normalizes once for both paths and rejects whitespace-only names before storage. Eleven audio API tests pass, including spaced/normalized retries, different-name409, blank400 and exactly one persisted answer/blob reference. Bounded independent review is CLEAN. It will ship in the #2886 follow-through; this does not reopen or reset the #2866 merge gate. Comments3975612383 and3975612391 are also implemented in the combined follow-through: retained verified versions keep their previously-confirmed library label after board deletion, and move previews mark both saved source and destination lanes. Ten combined browser journeys plus the separate comparison journey passed at6c6a344a4; full frontend6418passed/3existing skips, migration chain and model consistency pass. Full backend is finishing its CLI/architecture tail. Required #2866 CI has reached E2E Smoke with all completed jobs green. No merge is claimed yet. |
|
Refreshed onto current main f0d4ebf at bc36674. The merge adds only the seven already-landed CI observer files from #2868; backend/frontend/deploy/infrastructure are unchanged. All 300 continuation contract tests, documentation links, operations governance and diff checks pass. One bounded fresh-context integration review is CLEAN. Required run34447370502 is qualifying this exact head. The previous required run34440388603 passed; it is not being substituted for the refreshed-head gate. Later overhaul features remain on #2886 and its children so this delivery can proceed independently. |
Found by sweeping every merge since 2026-09-08 against the full ci/policy.v1.json controlPaths list rather than the three prefixes I had been using. That sweep found #2866, a product PR that added 15 lines to scripts/deploy/audio-response-policies.test.mjs and thereby became an R4 control-plane change — with nothing telling the agent, because this rule's paths: frontmatter listed only .github/**, ci/** and scripts/ci/**. Policy declares 36 control paths. The rule loaded on 3 of them. So an agent editing scripts/deploy/**, scripts/security/**, the release build scripts, backend/Directory.Packages.props, any package manifest or lockfile, global.json, .gitleaks.toml or .semgrep/** got no CI-control guidance at all: not the hosted-only proving rule, not the shadow-gate rule, not the merge gate this PR adds. The frontmatter now mirrors controlPaths entry for entry, with .github/** kept from the earlier scope so .github/ISSUE_TEMPLATE/** and similar still load the region's guidance. The body states that ci/policy.v1.json is the authority and this is a mirror, and that adding a control path there means adding it here in the same PR or the rule silently stops loading for it. Verified: the frontmatter parses as YAML with 37 paths (unparseable frontmatter is dropped silently, so this was checked rather than assumed); docs-governance and doc-links pass. Refs #2324, #2337.
…s one Re-ran the sweep against all 36 ci/policy.v1.json control paths instead of the three prefixes I had used, and caught #2866 (384d8df, 2026-09-10T07:26:11Z): a product PR from the overhaul train that added 15 lines to scripts/deploy/audio-response-policies.test.mjs, a declared control path. This one is a different failure mode from the other eleven. They declared the gate and merged anyway. #2866 never claimed to be control-plane because nothing told it: .claude/rules/ci-control.md declared only .github/**, ci/** and scripts/ci/**, so the region's rules never loaded for scripts/deploy/** at all. PR #2925 widens that frontmatter to mirror all 36 paths. Recorded on the row because it bears directly on question (b): part of what is failing here is notice, not only compliance, and a rule that does not reach two-thirds of its own region is evidence for amending rather than for enforcing harder. Refs #2337, #2336.
… J.3 Fresh-context review of the three previously unreviewed commits on this PR. All three findings are on text I added. HIGH: the Changelog entry said "Eleven control-plane PRs" while listing twelve and while the row it summarises says twelve in three places - the heading, question (a), and the arithmetic paragraph. The sequence is visible: one commit took 8 to Eleven, the next added #2866 to the list but not to the word. A maintainer reading the Changelog first would take eleven as the count and then be unable to reconcile 11 + 1 + 1 against the row's fourteen. Corrected to twelve. MEDIUM: "three of the 36 paths" understated the old coverage. The three globs .github/**, ci/** and scripts/ci/** reach 10 of the 36 declared entries, not 3, because .github/** alone covers eight of them. Measured against ci/policy.v1.json. The substance holds - 26 entries were unreached - but the number was wrong and I had repeated it. MEDIUM: "#2866 had no way to know it had become control-plane" claimed more than was verified. scripts/deploy/** is declared in the checked-in ci/policy.v1.json, so the path was discoverable; what is verified is that the auto-loading rules region never fired. Both the row and the Changelog now state the mechanism instead of the agent's state of knowledge. LOW, fixed while here: the heading read 2026-09-08 to 2026-09-10 when the earliest merge it lists is #2832 on 2026-09-09. 2026-09-08 is the sweep window and the date of J.1 and J.2, which sit directly above it. Declined: the reviewer noted "nineteen ... outside the per-PR review" is narrower than the phrase suggests, since it excludes the sixteen merged under the 2026-09-06 and 2026-09-09 delegations whose post-hoc review is still owed. The exclusion is deliberate and conservative - delegated merges were not violations - and including them would strengthen rather than weaken the row's argument.
Taskdeck now keeps original evidence, personal planning and authoritative proposal previews connected across Classic, Studio, Companion and Unified. Users can explicitly select original answers for chat, retain and revisit original recordings, choose personal work without changing card deadlines, and inspect a proposed change on the board before opening Review. Grove and Legacy remain selectable.
This combines the reviewed original-source, audio/library, comparison, companion, board-overlay, planning and portability continuations through #2877, preserving their commits. Continues #2808; the separate follow-through delivery is #2886. No approval/Apply bypass, implicit transcription or private-source retrieval is introduced.
Validation:
Required exact-head CI is run34440388603 at320acd7edd92a97fa00585f1fd94775aa883e9ef. Hosted completion remains required before merge. All local data and services were synthetic; the primary checkout and working database were untouched. Physical microphones/devices, live-provider usefulness, restoration and release acceptance remain separate. OUTSTANDING_TASKS.md owner decisions remain open.
Detailed proof: docs/product/WORKSPACE_OVERHAUL_VALIDATION.md and the retained overhaul evidence directory.