[feature] the phone names and files a recording once it has read it - #347
Merged
Merged
Conversation
Every recording made on the phone was called "Meeting Sep 7, 3:20 PM" and there was no way to change it: `rename` appears nowhere in the iOS target, so the library filled with rows nobody could tell apart and nothing could be done about it from the device that made them. Filing had the same shape as the desktop's old ingest wizard — `SaveDestination` asks which folder to use before the recording exists, at the one moment nobody knows what the meeting was about. The desktop answered this in v0.27.0 (#327): once there is a transcript, a pass reads it and proposes a title plus 2-3 candidate folders. That pass is desktop client code, so a phone recording only got an honest name if the user later opened it on a Mac. Now the phone runs its own. It is a native port, not a shared service. `TranscriptPolisher` already mirrors the desktop's polish prompt word-for-word over the cloud's OpenAI-compatible chat endpoint, and `FilingSuggester` mirrors `src/lib/ai/filing.ts` the same way: same SYSTEM prompt, same folder menu, and a line-for-line port of `resolveFilingFolders`, which is where the product's rules actually live (cap at 3, an existing folder beats the model claiming "new", at most one folder ever created). The alternative was moving the pass into the worker, which is the cleaner architecture and a cross-repo change this did not need. Three things the phone does differently, each for a reason: The pass waits for the UPLOAD to settle rather than for the microphone to stop. A rename or a re-file is a write against a recording the server already holds, so anything earlier produces an answer with nothing to apply it to. A recording auto-shared to an org is skipped entirely. The copy the user will open is the org one and there is no org rename endpoint on the phone — the desktop skips the same case for the same reason. The transcript is capped at 24k characters, head and tail with the middle marked as elided. Dictation is capped at 120s and never had to think about this; an hour of conversation is past what the fast lane will accept, and an over-long request is not a worse suggestion, it is no suggestion. Acceptance is derived from live state, never stored: the title row retires itself once the recording is called that, a folder row once it is filed there. Every write — accept or dismiss — sets `filingSuggested` on the meta, which is what stops the Mac asking again about a recording the user already dealt with on their phone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
✅ SonarQube Quality Gate passed — pathorsAI_parley0 open issues on this PR. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
A recording finished on the phone now proposes what it should be called and which folder it belongs in, as a card on the record screen. Accepting the name renames it; accepting a folder files it there, creating the folder first if it does not exist yet. Before this, iOS could do neither — every recording was called
Meeting Sep 7, 3:20 PMandrenameappeared nowhere in the iOS target at all.Why
Closes #343.
The desktop got this in v0.27.0 (#327): once a recording is transcribed, a pass reads the transcript and proposes a title plus 2-3 candidate folders. That pass is desktop client code (
src/lib/analysis/filingRun.ts→src/lib/ai/filing.ts), dispatched when a recording loads into replay, so a phone recording only ever got an honest name if the user later opened it on a Mac.This is a native port rather than a shared backend service, following the precedent already in the tree:
TranscriptPolishermirrors the desktop's polish prompt word-for-word over the cloud's OpenAI-compatible chat endpoint, andFilingSuggestermirrorssrc/lib/ai/filing.tsthe same way — sameSYSTEMprompt, same folder menu, and a line-for-line port ofresolveFilingFolders, which is where the product's rules actually live (cap at 3, an existing folder beats the model claimingisNew, at most one folder ever created). Moving the pass into the worker is the cleaner architecture, but it is a cross-repo change this did not need.What the phone does differently, and why
filingRun.tsbails onreplayReadOnly).filingSuggestedon the meta, including a dismiss. That is the flag the desktop reads to decide whether its own pass has already been spent, so a recording dealt with on the phone is not asked about again on the Mac.How it was verified
bunx tsc --noEmitpassesbunx vitest runpasses (373 tests) — no TypeScript changed, this is the baseline holdingFilingSuggesterTests.swiftcovers everyresolveFoldersrule, the JSON extraction, the title gate, transcript rendering and capping, and the request shapeswift build --package-path ios/ParleyKitpasseszh-Hantandeninios/App/Parley/Localizable.xcstrings(9 keys)The verification gap, stated plainly
swift testcould not be run on the machine that wrote this: it fails withno such module 'XCTest', identically on a clean tree, because only Command Line Tools are installed and the CLT SDK ships no XCTest. The app target could not be compiled either, for the same reason — that needs Xcode and XcodeGen.What was done instead:
@testable: everyresolveFoldersrule, org folders never offered as a home, JSON extraction (fenced / preambled / brace-inside-string / truncated), the title gate including Simplified-drift, the transcript cap, and prompt assembly. All passed.FilingSuggesterTests.swiftwas type-checked against the real built module using a stand-in XCTest, so it will compile when CI runs it.ForEach(_:id: \.self)over indices,.overlay { RoundedRectangle().strokeBorder() },.contentShape(RoundedRectangle(...)),.background(_:in:),ProgressView().controlSize(.mini)) was type-checked in isolation against the macOS SDK.That still leaves the app target uncompiled, and
ci.ymldoes not build iOS — only the tag-triggeredios-release.ymldoes. So a type error in the SwiftUI would pass this PR's checks and surface at release time. Please open it in Xcode before merging rather than trusting a green tick here. Auto-merge has deliberately not been set for that reason.No screenshots for the same reason: the card cannot be rendered here.
Known limits
MeetingUploader.syncPending(queued while offline) have no screen to put a card on.filingSuggestedunset — so the recording simply gets the desktop's offer later, which is the intended fallback rather than a lost outcome.createFoldermints the id client-side and sends{id, name, createdAt}, matching the desktop'screateCloudFolderexactly. The server's response shape for personal folders is not pinned anywhere in this repo (only the org route documents{folder}), so the client decodes that envelope when present and otherwise returns what it asked for. Worth a glance from someone who can see the worker.Delivery
Merging does not put this on anyone's phone — it ships with the next App Store submission.