Use Plugin: Trae Remote Official - #1
Conversation
Co-authored-by: traeagent <traeagent@users.noreply.github.com>
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
|
Analysis CompleteGenerated ECC bundle from 1 commits | Confidence: 55% View Pull Request #2Repository Profile
Changed Files (9)
Top hotspots
Top directories
Analysis Depth Readiness (commit-history, 7%)ECC Tools uses this to decide whether recommendations should stay at commit-history/setup guidance or expand into CI, security, harness, reference-set, AI-routing, and team backlog work.
Reference Set Readiness (0/7, 0%)
Likely Future Issues (1)
Suggested Follow-up Work (1)
Copy-ready bodies test: add browser coverage for app/src/components/CameraCapture.tsx ## Summary
- Add browser or end-to-end coverage for the recently changed user-facing surface.
## Why
- Backfill browser coverage before another user-facing UI change lands on the touched surface.
## Touched paths
- `app/src/components/CameraCapture.tsx`
## Validation
- Add or extend browser / e2e coverage for the changed component, page, or flow.
- Exercise the visible user journey that depends on the touched UI surface.Review Activity (1 reviews, 0 inline comments, 0 unresolved threads)
Latest reviewer states
Review Follow-up Signals (1)
Recommended next actions
Generated Instincts (18)
After merging, import with: Files
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b675211c6b
ℹ️ 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".
| constructor() { | ||
| this.pubky = new Pubky(); | ||
| this.restoreSession(); | ||
| this.initPromise = this.initPubky(); |
There was a problem hiding this comment.
Await session restore before reporting signed out
With a saved varsha_pubky_session, the app's mount effect reads pubkySync.isSignedIn() synchronously to set isPubkyAuthed, but this constructor now only starts the dynamic import/restoreSession() in the background. On a refresh, session is still null at that read and no later state update flips it, so previously signed-in users are shown the Pubky sign-in flow and cannot backup/restore until signing in again; make the mount path await pubkySync.ready()/restore before deriving auth state.
Useful? React with 👍 / 👎.
| const mime = f.type?.startsWith("image/") ? "image/jpeg" : (f.type || "application/octet-stream"); | ||
| const name = f.type?.startsWith("image/") | ||
| ? f.name.replace(/\.[^.]+$/, "") + ".jpg" | ||
| : f.name; |
There was a problem hiding this comment.
Preserve MIME for uncompressed images
When compressImage returns the original blob (it explicitly does this for GIF/SVG and for images where JPEG is larger), these lines still label the stored bytes as image/jpeg and rename to .jpg just because the original MIME starts with image/. Uploading an SVG/GIF or small PNG then stores non-JPEG bytes under a JPEG data URL/name, which can make previews/downloads fail or change the file type; derive type/name from the blob actually returned, or only coerce when a JPEG was produced.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
11 issues found across 9 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="app/src/engine/vault.ts">
<violation number="1" location="app/src/engine/vault.ts:12">
P2: `StoredFile` interface is defined identically in both `vault.ts` and `imageCompress.ts`. This creates a maintenance risk — if one definition is updated (e.g., adding a field), the other becomes incompatible silently (TypeScript structural typing would still let mismatched objects pass through). Import `StoredFile` from `vault.ts` into `imageCompress.ts` instead of re-declaring it.</violation>
<violation number="2" location="app/src/engine/vault.ts:203">
P2: Updating an attachment through the still-public legacy `file` field now silently discards it. Normalize this patch like `add` does (or remove `file` from the update API) so legacy callers cannot report a successful update with no attachment saved.</violation>
</file>
<file name="app/src/engine/imageCompress.ts">
<violation number="1" location="app/src/engine/imageCompress.ts:10">
P3: Attachment encoding now has two identical `b64` implementations, so fixes to encoding or chunking can diverge. Reuse one shared helper instead of maintaining copies.</violation>
<violation number="2" location="app/src/engine/imageCompress.ts:71">
P1: GIF, SVG, and images whose JPEG output is larger are stored as their original bytes but advertised and downloaded as JPEG. Derive MIME/name from `compressed.type` so previews and downloads retain a matching format.</violation>
</file>
<file name="app/src/engine/guardians.ts">
<violation number="1" location="app/src/engine/guardians.ts:5">
P2: After an RNG setup failure, later generate/combine calls proceed instead of consistently failing because `secretsLib` is checked before `secretsError`. Check the cached error first (or clear `secretsLib`) so guardian codes are not processed after failed initialization.</violation>
</file>
<file name="app/src/components/CameraCapture.tsx">
<violation number="1" location="app/src/components/CameraCapture.tsx:38">
P1: Rapid camera switches or closing while permission is pending can leave a prior camera stream running. Track/cancel each pending `getUserMedia` request and stop a stream if its request is stale before assigning it to the video.</violation>
<violation number="2" location="app/src/components/CameraCapture.tsx:228">
P2: Pressing Done while photo conversion is in progress drops the just-taken page. Keep Done unavailable until `isCapturing` is false, or await the capture before calling `onCapture`.</violation>
</file>
<file name="app/src/identity/pubkySync.ts">
<violation number="1" location="app/src/identity/pubkySync.ts:17">
P2: Persisted Pubky sessions can appear signed out after a reload because initialization now waits for a dynamic import while `App` snapshots `isSignedIn()` only once. Await `pubkySync.ready()` before that snapshot, or notify the UI when session restoration completes.</violation>
<violation number="2" location="app/src/identity/pubkySync.ts:61">
P2: Auth callbacks can leave the app initialization flow unhandled when Pubky is unavailable, since `requirePubky()` sits outside `awaitAuth()`'s error-to-result conversion. Include availability validation in that `try` or return a failure result for it.</violation>
</file>
<file name="app/src/App.tsx">
<violation number="1" location="app/src/App.tsx:819">
P2: Selecting attachments leaks a new blob URL on every dialog re-render, including each form edit; large photos can retain substantial memory until the page closes. Keep one URL per picked file and revoke it when that file is removed or the dialog unmounts.</violation>
<violation number="2" location="app/src/App.tsx:943">
P2: Large or numerous PDF selections can exhaust browser memory/storage and make Save fail or freeze the vault UI. Restore explicit per-file and aggregate attachment limits before adding files to `pickedFiles`.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| export async function fileToStored(f: File): Promise<StoredFile> { | ||
| const compressed = await compressImage(f); | ||
| const buf = new Uint8Array(await compressed.arrayBuffer()); | ||
| const mime = f.type?.startsWith("image/") ? "image/jpeg" : (f.type || "application/octet-stream"); |
There was a problem hiding this comment.
P1: GIF, SVG, and images whose JPEG output is larger are stored as their original bytes but advertised and downloaded as JPEG. Derive MIME/name from compressed.type so previews and downloads retain a matching format.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/engine/imageCompress.ts, line 71:
<comment>GIF, SVG, and images whose JPEG output is larger are stored as their original bytes but advertised and downloaded as JPEG. Derive MIME/name from `compressed.type` so previews and downloads retain a matching format.</comment>
<file context>
@@ -0,0 +1,83 @@
+export async function fileToStored(f: File): Promise<StoredFile> {
+ const compressed = await compressImage(f);
+ const buf = new Uint8Array(await compressed.arrayBuffer());
+ const mime = f.type?.startsWith("image/") ? "image/jpeg" : (f.type || "application/octet-stream");
+ const name = f.type?.startsWith("image/")
+ ? f.name.replace(/\.[^.]+$/, "") + ".jpg"
</file context>
| setError(""); | ||
| stopCamera(); | ||
| try { | ||
| const stream = await navigator.mediaDevices.getUserMedia({ |
There was a problem hiding this comment.
P1: Rapid camera switches or closing while permission is pending can leave a prior camera stream running. Track/cancel each pending getUserMedia request and stop a stream if its request is stale before assigning it to the video.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/components/CameraCapture.tsx, line 38:
<comment>Rapid camera switches or closing while permission is pending can leave a prior camera stream running. Track/cancel each pending `getUserMedia` request and stop a stream if its request is stale before assigning it to the video.</comment>
<file context>
@@ -0,0 +1,233 @@
+ setError("");
+ stopCamera();
+ try {
+ const stream = await navigator.mediaDevices.getUserMedia({
+ video: {
+ facingMode: { ideal: mode },
</file context>
| const next: VaultItem = { ...cur, ...patch, id: cur.id, createdAt: cur.createdAt, updatedAt: Date.now() }; | ||
| const cur = normalizeItem((await open(requireKey(), sealed)) as VaultItem); | ||
| const normalizedPatch: Partial<VaultItem> = { ...patch }; | ||
| delete normalizedPatch.file; |
There was a problem hiding this comment.
P2: Updating an attachment through the still-public legacy file field now silently discards it. Normalize this patch like add does (or remove file from the update API) so legacy callers cannot report a successful update with no attachment saved.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/engine/vault.ts, line 203:
<comment>Updating an attachment through the still-public legacy `file` field now silently discards it. Normalize this patch like `add` does (or remove `file` from the update API) so legacy callers cannot report a successful update with no attachment saved.</comment>
<file context>
@@ -165,16 +188,20 @@ export function createVault(storage: StorageAdapter): Vault {
- const next: VaultItem = { ...cur, ...patch, id: cur.id, createdAt: cur.createdAt, updatedAt: Date.now() };
+ const cur = normalizeItem((await open(requireKey(), sealed)) as VaultItem);
+ const normalizedPatch: Partial<VaultItem> = { ...patch };
+ delete normalizedPatch.file;
+ const next: VaultItem = { ...cur, ...normalizedPatch, id: cur.id, createdAt: cur.createdAt, updatedAt: Date.now() };
await storage.set("item:" + id, await seal(requireKey(), next));
</file context>
| if (secretsLib) return secretsLib; | ||
| if (secretsError) throw new Error("Shamir's Secret Sharing unavailable: " + secretsError); |
There was a problem hiding this comment.
P2: After an RNG setup failure, later generate/combine calls proceed instead of consistently failing because secretsLib is checked before secretsError. Check the cached error first (or clear secretsLib) so guardian codes are not processed after failed initialization.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/engine/guardians.ts, line 5:
<comment>After an RNG setup failure, later generate/combine calls proceed instead of consistently failing because `secretsLib` is checked before `secretsError`. Check the cached error first (or clear `secretsLib`) so guardian codes are not processed after failed initialization.</comment>
<file context>
@@ -1,31 +1,46 @@
- secrets.init(8, "browserCryptoGetRandomValues");
-} catch (e) {
+async function getSecrets(): Promise<any> {
+ if (secretsLib) return secretsLib;
+ if (secretsError) throw new Error("Shamir's Secret Sharing unavailable: " + secretsError);
try {
</file context>
| if (secretsLib) return secretsLib; | |
| if (secretsError) throw new Error("Shamir's Secret Sharing unavailable: " + secretsError); | |
| if (secretsError) throw new Error("Shamir's Secret Sharing unavailable: " + secretsError); | |
| if (secretsLib) return secretsLib; |
| <Button | ||
| variant="primary" | ||
| label={captured.length > 0 ? `Add ${captured.length} page${captured.length > 1 ? "s" : ""}` : "Done (no photos)"} | ||
| onClick={handleDone} |
There was a problem hiding this comment.
P2: Pressing Done while photo conversion is in progress drops the just-taken page. Keep Done unavailable until isCapturing is false, or await the capture before calling onCapture.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/components/CameraCapture.tsx, line 228:
<comment>Pressing Done while photo conversion is in progress drops the just-taken page. Keep Done unavailable until `isCapturing` is false, or await the capture before calling `onCapture`.</comment>
<file context>
@@ -0,0 +1,233 @@
+ <Button
+ variant="primary"
+ label={captured.length > 0 ? `Add ${captured.length} page${captured.length > 1 ? "s" : ""}` : "Done (no photos)"}
+ onClick={handleDone}
+ />
+ </Stack>
</file context>
| constructor() { | ||
| this.pubky = new Pubky(); | ||
| this.restoreSession(); | ||
| this.initPromise = this.initPubky(); |
There was a problem hiding this comment.
P2: Persisted Pubky sessions can appear signed out after a reload because initialization now waits for a dynamic import while App snapshots isSignedIn() only once. Await pubkySync.ready() before that snapshot, or notify the UI when session restoration completes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/identity/pubkySync.ts, line 17:
<comment>Persisted Pubky sessions can appear signed out after a reload because initialization now waits for a dynamic import while `App` snapshots `isSignedIn()` only once. Await `pubkySync.ready()` before that snapshot, or notify the UI when session restoration completes.</comment>
<file context>
@@ -1,42 +1,79 @@
constructor() {
- this.pubky = new Pubky();
- this.restoreSession();
+ this.initPromise = this.initPubky();
}
</file context>
| accept="image/*,application/pdf" | ||
| multiple | ||
| style={{ display: "none" }} | ||
| onChange={handleFilePick} |
There was a problem hiding this comment.
P2: Large or numerous PDF selections can exhaust browser memory/storage and make Save fail or freeze the vault UI. Restore explicit per-file and aggregate attachment limits before adding files to pickedFiles.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/App.tsx, line 943:
<comment>Large or numerous PDF selections can exhaust browser memory/storage and make Save fail or freeze the vault UI. Restore explicit per-file and aggregate attachment limits before adding files to `pickedFiles`.</comment>
<file context>
@@ -932,16 +929,70 @@ function AddDialog(props: {
+ accept="image/*,application/pdf"
+ multiple
+ style={{ display: "none" }}
+ onChange={handleFilePick}
+ />
+ <Stack direction="horizontal" gap={2} wrap="wrap">
</file context>
|
|
||
| const getThumbUrl = (f: StoredFile | File): string => { | ||
| if (f instanceof File) { | ||
| return URL.createObjectURL(f); |
There was a problem hiding this comment.
P2: Selecting attachments leaks a new blob URL on every dialog re-render, including each form edit; large photos can retain substantial memory until the page closes. Keep one URL per picked file and revoke it when that file is removed or the dialog unmounts.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/App.tsx, line 819:
<comment>Selecting attachments leaks a new blob URL on every dialog re-render, including each form edit; large photos can retain substantial memory until the page closes. Keep one URL per picked file and revoke it when that file is removed or the dialog unmounts.</comment>
<file context>
@@ -816,30 +765,78 @@ function RestorePassphraseDialog(props: { onClose: () => void; onRestore: (pass:
+
+ const getThumbUrl = (f: StoredFile | File): string => {
+ if (f instanceof File) {
+ return URL.createObjectURL(f);
+ }
+ return `data:${f.type};base64,${f.dataB64}`;
</file context>
| * Node >= 20), which is what lets the same engine ship as app, SDK, and ADK. | ||
| */ | ||
|
|
||
| export interface StoredFile { |
There was a problem hiding this comment.
P2: StoredFile interface is defined identically in both vault.ts and imageCompress.ts. This creates a maintenance risk — if one definition is updated (e.g., adding a field), the other becomes incompatible silently (TypeScript structural typing would still let mismatched objects pass through). Import StoredFile from vault.ts into imageCompress.ts instead of re-declaring it.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/engine/vault.ts, line 12:
<comment>`StoredFile` interface is defined identically in both `vault.ts` and `imageCompress.ts`. This creates a maintenance risk — if one definition is updated (e.g., adding a field), the other becomes incompatible silently (TypeScript structural typing would still let mismatched objects pass through). Import `StoredFile` from `vault.ts` into `imageCompress.ts` instead of re-declaring it.</comment>
<file context>
@@ -9,6 +9,12 @@
* Node >= 20), which is what lets the same engine ship as app, SDK, and ADK.
*/
+export interface StoredFile {
+ name: string;
+ type: string;
</file context>
| dataB64: string; | ||
| } | ||
|
|
||
| function b64(buf: ArrayBuffer | Uint8Array): string { |
There was a problem hiding this comment.
P3: Attachment encoding now has two identical b64 implementations, so fixes to encoding or chunking can diverge. Reuse one shared helper instead of maintaining copies.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/engine/imageCompress.ts, line 10:
<comment>Attachment encoding now has two identical `b64` implementations, so fixes to encoding or chunking can diverge. Reuse one shared helper instead of maintaining copies.</comment>
<file context>
@@ -0,0 +1,83 @@
+ dataB64: string;
+}
+
+function b64(buf: ArrayBuffer | Uint8Array): string {
+ const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
+ let s = "";
</file context>
Summary by cubic
Adds on‑device document scanning with compression and support for multiple attachments per item. Updates the vault engine, search, Pubky sync, and Guardians to support the new flow and improve reliability.
New Features
CameraCapture, with on-device JPEG compression; attach multiple photos/PDFs; thumbnails, carousel/lightbox, download/remove pages.Refactors
files: StoredFile[]; legacyfileis auto-migrated on read/import; export/import and search updated; tests added for multi-file and migration.secrets.js-34r7h;generateGuardianCodesandcombineGuardianCodesare now async.imageCompress.ts(centralized compression andfileToStored/blobToStored);ImportDialoguses a native file input.@synonymdev/pubky,otpauth,secrets.js-34r7h.Written for commit b675211. Summary will update on new commits.