Skip to content

feat: add in-browser text file editor modal#87

Merged
FSM1 merged 4 commits into
mainfrom
feat/text-editor-modal
Feb 10, 2026
Merged

feat: add in-browser text file editor modal#87
FSM1 merged 4 commits into
mainfrom
feat/text-editor-modal

Conversation

@FSM1

@FSM1 FSM1 commented Feb 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add a text editor modal for editing text files directly in the browser, with full client-side crypto round-trip: download → decrypt → edit → encrypt → re-upload → update folder metadata → unpin old CID
  • Add replaceFileInFolder service function and updateFile hook operation following existing patterns (rename, move)
  • Add "Edit" context menu option that appears only for text file extensions (~35 supported: .txt, .md, .json, .yaml, .js, .ts, .py, etc.)
  • Add Pencil design screen (19. Text Editor Modal) with wider 800px modal, monospace textarea, and status line
  • Add E2E page object (TextEditorDialogPage) and 5 test cases covering the full edit workflow

Test plan

  • Upload a .txt or .md file, right-click → verify "Edit" appears
  • Right-click a folder or .png → verify "Edit" does NOT appear
  • Click Edit → verify loading state → decrypted content appears in textarea
  • Modify content → verify "modified" indicator and save button enables
  • Click Save → verify saving state → dialog closes → file size updates
  • Re-open editor → verify edited content persisted (round-trip works)
  • Check Details → verify CID changed (confirms re-encryption)
  • Run E2E tests: pnpm --filter e2e test (Phase 5.5 tests)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • In-browser encrypted text editor with inline editing and Ctrl/Cmd+S; saves update file contents and persist across opens.
    • "Edit" option added to the file context menu for recognized text files.
  • UI / Design

    • Wider, terminal-styled text editor modal with status line and save/cancel controls.
  • Tests

    • End-to-end tests for editor visibility, load, edit, save, CID change and persistence.

Copilot AI review requested due to automatic review settings February 10, 2026 20:49
@coderabbitai

coderabbitai Bot commented Feb 10, 2026

Copy link
Copy Markdown

Walkthrough

Adds an in-browser text editor for encrypted text files: new Edit context-menu action, a TextEditorDialog modal that loads/decrypts/edits/re-encrypts and uploads content to IPFS, new folder/service APIs to replace file metadata, modal styling, and end-to-end tests for the editor flow.

Changes

Cohort / File(s) Summary
Context Menu & File Browser UI
apps/web/src/components/file-browser/ContextMenu.tsx, apps/web/src/components/file-browser/FileBrowser.tsx
Added optional onEdit callback to ContextMenu; added text-file detection (TEXT_EXTENSIONS, isTextFile) and wired Edit action to open TextEditorDialog.
Text Editor Component & Styling
apps/web/src/components/file-browser/TextEditorDialog.tsx, apps/web/src/styles/text-editor-dialog.css, apps/web/src/components/ui/Modal.tsx
Introduced TextEditorDialog component (load/decrypt, edit, dirty tracking, Ctrl/Cmd+S save, encrypt/upload, metadata update). Modal accepts className. Added dialog-specific styles.
Folder Management Services
apps/web/src/hooks/useFolder.ts, apps/web/src/services/folder.service.ts
Added updateFile() in useFolder hook and replaceFileInFolder() in folder service to replace file entry (new CID, encrypted key, IV, size), update sequence, refresh quota, and unpin old CID asynchronously.
E2E Testing
tests/e2e/page-objects/dialogs/text-editor-dialog.page.ts, tests/e2e/page-objects/dialogs/index.ts, tests/e2e/page-objects/file-browser/context-menu.page.ts, tests/e2e/tests/full-workflow.spec.ts
Added TextEditorDialogPage page object and export; extended ContextMenuPage with editOption()/clickEdit(); added Phase 5.5 tests covering editor open/load/edit/save and CID verification.
Design & Mockup
designs/cipher-box-design.pen
Added Text Editor Modal mockup (header, textarea, status line, Cancel/Save actions).

Sequence Diagram

sequenceDiagram
    participant User
    participant Dialog as TextEditorDialog
    participant Auth as Auth/Keypair
    participant IPFS
    participant FolderHook as useFolder
    participant FolderSvc as FolderService

    User->>Dialog: Open editor for text file
    Dialog->>Auth: derive keypair / request download
    Auth->>IPFS: fetch encrypted blob
    IPFS-->>Auth: encrypted blob
    Auth-->>Dialog: decrypted plaintext (display)

    User->>Dialog: Edit content and Save
    Dialog->>Auth: encrypt modified content
    Auth-->>Dialog: encrypted blob
    Dialog->>IPFS: upload encrypted blob
    IPFS-->>Dialog: new CID

    Dialog->>FolderHook: updateFile(parentId, {fileId, newCid, newFileKeyEncrypted, newFileIv, newSize})
    FolderHook->>FolderSvc: replaceFileInFolder(params)
    FolderSvc-->>FolderHook: {newSequenceNumber, oldCid}
    FolderHook->>IPFS: unpin(oldCid) (async)
    FolderHook-->>Dialog: save complete
    Dialog->>User: close
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: add in-browser text file editor modal' directly and clearly summarizes the main change: introducing a new in-browser text editor modal component. The title accurately reflects the primary feature being added.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/text-editor-modal

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
apps/web/src/hooks/useFolder.ts (1)

573-573: Consider logging swallowed unpin errors at debug level.

The empty .catch(() => {}) silently discards failures. If unpin consistently fails (e.g., auth token expired), there'd be no signal. A minimal log would aid debugging without blocking the user flow.

Suggested change
-        unpinFromIpfs(oldCid).catch(() => {});
+        unpinFromIpfs(oldCid).catch((err) => {
+          console.warn('Failed to unpin old CID:', oldCid, err);
+        });
tests/e2e/tests/full-workflow.spec.ts (1)

804-824: Test 5.5.1 only verifies folders lack "Edit" — consider adding a non-text file assertion if fixtures allow.

The PR states Edit is shown only for ~35 text extensions, but this test only confirms a folder doesn't show "Edit." Since all files in the test suite are .txt, there's no non-text file (e.g., .png, .zip) to assert against. If future test data includes binary file types, extending this test would strengthen coverage. Fine as-is given current fixtures.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@apps/web/src/components/file-browser/FileBrowser.tsx`:
- Around line 36-88: The isTextFile logic misses files named like
Dockerfile/Makefile because TEXT_EXTENSIONS only contains dot-prefixed entries;
update the check in isTextFile (or the set) so bare filenames are recognized:
either add dot-less entries ("dockerfile","makefile", "gitignore",
"editorconfig", etc.) to TEXT_EXTENSIONS, or change isTextFile to first test
TEXT_EXTENSIONS.has(lower) (full name) and then fallback to the existing
extension logic (compute lastDot and test
TEXT_EXTENSIONS.has(lower.slice(lastDot))). Reference TEXT_EXTENSIONS and
isTextFile when making the change.

In `@apps/web/src/components/file-browser/TextEditorDialog.tsx`:
- Line 112: The blob creation uses encrypted.ciphertext.buffer which may include
the whole underlying ArrayBuffer and corrupt data; update the Blob construction
in TextEditorDialog (the code around encrypted.ciphertext usage) to pass the
Uint8Array view directly (i.e., pass encrypted.ciphertext) or, if you must use
the underlying buffer, slice it using
encrypted.ciphertext.buffer.slice(encrypted.ciphertext.byteOffset,
encrypted.ciphertext.byteOffset + encrypted.ciphertext.byteLength) so the Blob
contains only the exact ciphertext bytes.
- Around line 173-178: The JSX currently contains a literal "// {lineCount} ..."
which renders as visible text and is misinterpreted as a JS comment; in the
TextEditorDialog component update the span to build that status string as a JSX
expression instead of a bare comment-like token (e.g., replace the raw "//
{lineCount} ..." with a string expression that includes lineCount and
pluralization like {`// ${lineCount} ${lineCount === 1 ? 'line' : 'lines'} |
utf-8`} and keep the isDirty suffix logic intact), ensuring the status line is
explicit and silences the linter.
🧹 Nitpick comments (5)
apps/web/src/styles/text-editor-dialog.css (2)

38-53: Consider responsive behavior for smaller viewports.

The textarea has min-height: 400px and the modal has max-width: 800px. On mobile/small screens, the 400px min-height could push content off-screen or cause awkward scrolling, especially combined with the max-height: calc(90vh - 60px) on the body. Consider adding a media query to reduce min-height on narrow viewports.


55-57: Hardcoded color in focus glow.

rgba(0, 208, 132, 0.05) is a hardcoded brand color. The rest of the file consistently uses CSS custom properties. Consider deriving this from a variable for theme consistency.

apps/web/src/components/file-browser/ContextMenu.tsx (1)

187-193: Edit and Rename share the same icon ( / ✎).

Line 190 uses ✎ for Edit, and line 197 uses the same ✎ for Rename. Consider using a distinct icon for Edit to avoid visual confusion between the two adjacent menu items.

apps/web/src/hooks/useFolder.ts (1)

554-563: Minor: modifiedAt set both in the service and locally with different Date.now() calls.

replaceFileInFolder (folder.service.ts line 806) sets modifiedAt: Date.now() on the IPNS-published metadata, and this local state update (line 562) calls Date.now() again, producing a marginally different timestamp. The IPNS version is authoritative and will be reconciled on sync, so this is benign — but you could capture the timestamp from the service result for perfect consistency, or just note this is intentional.

apps/web/src/components/file-browser/TextEditorDialog.tsx (1)

104-109: No existing Web Worker encryption pattern in codebase — unnecessary to reference consistency.

The coding guidelines recommend Web Workers for file encryption to avoid blocking the UI thread. However, since there is no established worker-based encryption path elsewhere in the codebase and text files edited in the modal are small, adding a Web Worker here would introduce unnecessary complexity without practical benefit. If a worker-based encryption pattern is established elsewhere for handling large files, revisit this at that time.

Comment thread apps/web/src/components/file-browser/FileBrowser.tsx
Comment thread apps/web/src/components/file-browser/TextEditorDialog.tsx Outdated
Comment thread apps/web/src/components/file-browser/TextEditorDialog.tsx

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an in-browser text editor modal to the web file browser, enabling editing of text files with a full client-side crypto round-trip (download/decrypt → edit → encrypt/upload → update IPNS metadata), plus E2E coverage and design updates.

Changes:

  • Introduces TextEditorDialog UI + styling and wires an “Edit” option into the file context menu for text-like filenames.
  • Adds folder-metadata update support via replaceFileInFolder and exposes an updateFile operation in useFolder.
  • Expands Playwright E2E suite with a text editor page object and full workflow tests; updates Pencil design source.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/e2e/tests/full-workflow.spec.ts Adds Phase 5.5 E2E tests covering open/edit/save/CID-change/persistence checks.
tests/e2e/page-objects/file-browser/context-menu.page.ts Adds Playwright helpers for selecting the new “Edit” context menu item.
tests/e2e/page-objects/dialogs/text-editor-dialog.page.ts New page object encapsulating editor modal interactions and assertions.
tests/e2e/page-objects/dialogs/index.ts Exports the new dialog page object.
designs/cipher-box-design.pen Adds the “19. Text Editor Modal” design frame to the Pencil source of truth.
apps/web/src/styles/text-editor-dialog.css New CSS for the editor modal variant (width, textarea, status line, loading/error).
apps/web/src/services/folder.service.ts Adds replaceFileInFolder to update file entry fields and republish folder metadata to IPNS.
apps/web/src/hooks/useFolder.ts Adds updateFile operation that updates store state, unpins old CID, refreshes quota.
apps/web/src/components/ui/Modal.tsx Adds className prop to support modal variant styling via the backdrop.
apps/web/src/components/file-browser/TextEditorDialog.tsx New modal implementing download/decrypt/edit/encrypt/upload/update flow.
apps/web/src/components/file-browser/FileBrowser.tsx Adds text-file detection, passes onEdit, and mounts TextEditorDialog.
apps/web/src/components/file-browser/ContextMenu.tsx Adds “Edit” menu item support via optional onEdit callback.
.planning/todos/done/2026-01-23-simple-text-file-editor-modal.md Documents the completed UI todo and intended flow.

Comment thread apps/web/src/styles/text-editor-dialog.css
Comment thread apps/web/src/components/file-browser/FileBrowser.tsx
Comment thread apps/web/src/hooks/useFolder.ts
Comment thread apps/web/src/components/file-browser/TextEditorDialog.tsx Outdated
FSM1 and others added 4 commits February 10, 2026 22:36
Moved todo from pending to done — beginning implementation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a text editor modal that allows editing text files directly in the
browser. The full flow: download → decrypt → edit → encrypt → re-upload →
update folder metadata → unpin old CID.

- Add replaceFileInFolder service and updateFile hook operation
- Create TextEditorDialog component with loading/saving/error states
- Add "Edit" context menu option for text file extensions
- Add Pencil design screen for the text editor modal
- Add E2E page object and 5 test cases (Phase 5.5)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix potential data corruption: avoid using .buffer directly on
  Uint8Array sub-views by slicing to a clean copy first
- Support extensionless text filenames (Dockerfile, Makefile, etc.)
  via a separate TEXT_FILENAMES set
- Wrap JSX comment text in braces to satisfy noCommentText lint rule
- Fix JSDoc: handleUpdateFile returns Promise<void>, not old CID
- Use #0a0a0a textarea background to match Pencil design

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Test 3.7 (page reload preserves session) involves a full page reload +
re-authentication + IPNS sync chain that can exceed the default 30s
test timeout in CI. Increase to 90s to match the 60s locator timeout
with headroom.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@FSM1 FSM1 force-pushed the feat/text-editor-modal branch from bb85ba8 to f166716 Compare February 10, 2026 21:36
@FSM1 FSM1 merged commit 04bfc8c into main Feb 10, 2026
7 of 8 checks passed
@FSM1 FSM1 deleted the feat/text-editor-modal branch February 10, 2026 21:41
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