Skip to content

[feature] a finished recording proposes its own name and folder - #327

Merged
Lanznx merged 2 commits into
mainfrom
feature/filing-suggestion
Sep 2, 2026
Merged

[feature] a finished recording proposes its own name and folder#327
Lanznx merged 2 commits into
mainfrom
feature/filing-suggestion

Conversation

@YJack0000

Copy link
Copy Markdown
Contributor

What this changes

After a recording is transcribed, a card above the report page's filing bar proposes a better title for it plus 2–3 candidate folders, each one click to accept. At most one candidate may be a folder that does not exist yet.

Why

Both doors into a finished recording name it badly: a live meeting is saved as 即時會議 · <date> and an upload keeps its file name, so the library fills with rows nobody can tell apart. Filing is worse — the ingest wizard asks which folder to use before transcription, at the one moment when nobody yet knows what the meeting was about. IngestWizard.tsx already carries a comment conceding that this is what produces "recordings needing after-the-fact filing". This is the after-the-fact answer.

Design notes

It rides the cheap realtime lane and has no upstream dependency. The transcript alone is its input, so it dispatches in the same tick as the findings pass rather than queueing behind the expensive one — the suggestion should land while the user is still looking at the recording. It is also the one stage a realtime-only user ever gets.

findings ──done──▶ action items ──settled──▶ brief
     └────done──▶ delivery
filing   (no upstream — the transcript alone is its input)

A stage, but not an artifact. The titlebar chip counts four report artifacts and must keep counting four; a suggestion is a prompt to the user, not a section of the report. The split is carried in the types (StudyStageKey vs StudyArtifactKey) so the compiler enforces it, rather than a filter someone has to remember to write.

Acceptance state is derived, never stored. The title row hides once the recording is already called that; a folder chip hides once it is already filed there. So renaming from the titlebar, or filing from the bar below, retires the row just as well as the card's own buttons — an applied flag would only be a second copy of that fact, free to drift.

What is stored is filingSuggested, for the same reason analyzed exists: a null filingSuggestion cannot distinguish "never ran" from "ran, and the user dealt with it", and without the flag every reopen would re-spend the pass.

The model is not trusted with the product rules. resolveFilingFolders is pure and enforces them regardless of what comes back: at most one new folder, at most three chips, the registry's spelling wins over the model's, and a model claiming isNew about a folder that already exists resolves to the existing one.

A write race this also fixes

useRefile now resolves when the move has landed. setEntryFolder and the study persists are both read-modify-write over one meta.json; started concurrently, the later write reads a pre-move copy and silently drops the folder change. The store already shows the new folder, so it only surfaces on the next load.

Storage shape

No DB migration. Two optional fields are added to HistoryEntry (filingSuggestion, filingSuggested), which is persisted as opaque JSON in meta.json and pushed to the cloud verbatim — older entries simply omit them and read as "never ran".

How it was verified

  • bunx tsc --noEmit passes
  • bunx vitest run passes — 27 files, 285 tests
  • Ran the app (bun run tauri dev) and exercised the change
  • Added or updated tests — 10 new for resolveFilingFolders, 5 new for the pipeline scheduling
  • Added new user-facing strings to both zh-TW and en in src/i18n/messages.ts (7 keys, parity checked)

Not yet run in the app. The logic and scheduling are covered by tests, but the card has not been rendered on screen — the violet accent, chip wrapping and dark mode are unverified. Screenshots to follow before this merges.

Screenshots

Pending — see the note above.

Both doors into a finished recording name it badly: a live meeting is
saved as "即時會議 · <date>" and an upload keeps its file name, so the
library fills with rows nobody can tell apart. Filing is worse — the
ingest wizard asks which folder to use BEFORE transcription, at the one
moment when nobody yet knows what the meeting was about.

So the answer is produced afterwards. A new pass reads the transcript and
proposes a title plus 2-3 candidate folders, offered as a card above the
report page's filing bar. It rides the cheap realtime lane (like
meetingKind) and has NO upstream dependency — the transcript alone is its
input, so it dispatches in the same tick as the findings pass rather than
queueing behind the expensive one. That also makes it the one stage a
realtime-only user ever gets.

Two things worth knowing about the design:

The suggestion is a stage but NOT a report artifact — the titlebar chip
counts four artifacts and must keep counting four, so the split is carried
in the types (StudyStageKey vs StudyArtifactKey) rather than by a filter
someone has to remember to write.

Acceptance state is DERIVED, never stored: the title row hides once the
recording is already called that, a folder chip hides once it is already
filed there. Renaming from the titlebar or filing from the bar below
therefore retires the row too. What IS stored is `filingSuggested`, for
the same reason `analyzed` exists — a null suggestion can't distinguish
"never ran" from "ran, and the user dealt with it", and without the flag
every reopen would re-spend the pass.

useRefile now resolves when the move has landed. Both setEntryFolder and
the study persists are read-modify-write over one meta.json; started
concurrently, the later write reads a pre-move copy and silently drops the
folder change — visible only after a reload, since the store already shows
the new folder.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

✅ SonarQube Quality Gate passed — pathorsAI_parley

0 open issues on this PR.

- S3735: applyTitle is synchronous now, so the click handler can be passed
  to onClick directly instead of discarding a promise in expression
  position. The rejection is handled inside the callback either way.
- S3776: the folder-name index moves into its own function, taking
  resolveFilingFolders back under the cognitive-complexity limit and
  giving the "models re-type names rather than copying them" rationale a
  place to sit next to the code it explains.
- S4624: the folder list is built into a local before it is interpolated,
  rather than nesting a template literal inside one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Lanznx
Lanznx merged commit 3ab17d4 into main Sep 2, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants