Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@ and the live Diff settings preview.
- **Command palette (⌘K)** — fuzzy search across commands, branches, tags,
files, commits, and recent repos, with scope filtering and full keyboard +
screen-reader operability.
- **Work file documents** — edit syntax-highlighted working-tree files or
inspect historical source read-only; `--follow` history,
- **Work file documents** — edit working-tree files with Pierre Diffs edit mode
(unsaved drafts persist while navigating) or inspect historical source read-only; `--follow` history,
compare any two revisions, blame, and rendered previews for markdown and SVG; the Files tree
uses the local filesystem listing directly, including muted Git-ignored
paths, while overlaying current Git-state colors and recognizable language
Expand Down
10 changes: 4 additions & 6 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2220,14 +2220,12 @@ loadable while `.git` stays inaccessible; the snapshot/status hot path remains
unchanged.

**Syntax-highlighted file editing shipped (2026-07-19):** Opening an existing
UTF-8 working-tree file from Files now mounts a lightweight editor backed by
Pierre's shared Shiki tokenizer. Save and Mod+S explicitly write through
UTF-8 working-tree file from Files now mounts Pierre Diffs edit mode. Save and Mod+S explicitly write through
`repo_file_write`; the core preserves consistent CRLF endings and
rejects stale, traversal, symlink, binary, non-UTF-8, and oversized writes.
Historical revisions remain read-only. The editor projects the last Shiki token
colors onto each new buffer immediately, so background retokenization never
causes a plain-text flash while typing. Its in-memory buffer is LF-normalized
for textarea/Shiki agreement while the core retains and restores the exact CRLF
Historical revisions remain read-only. Unsaved working-tree drafts now stay in
memory when users switch files, tabs, repositories, or Work panes, but writes
remain explicit-only; the core retains and restores the exact CRLF
disk form on save.

**Files creation toolbar refined (2026-07-19):** The separate New file and New
Expand Down
16 changes: 7 additions & 9 deletions TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1208,16 +1208,14 @@ Detailed comparison and sequencing: [`docs/git-client-1.0-audit.md`](./docs/git-
- ☑ Tab strip + header (opened via `selectFile` from the Files tab / palette;
a Close action returns to Local Changes)
- ☑ Content tab — working-tree (or revision) content via `repo_file_content`.
Existing complete UTF-8 working-tree files edit through the syntax-highlighted
`HighlightedEditor` and save through stale-checked `repo_file_write`; revisions,
binaries, oversized files, and non-UTF-8 text stay on Pierre's read-only
`<File>`. Optimistic token projection keeps the last Shiki colors attached
to unchanged text during every edit—there is no plain-text refresh frame—and
reconstructs CRLF token streams without tripping the plain-text fallback.
Existing complete UTF-8 working-tree files edit through Pierre Diffs edit mode
(`EditProvider`/`Editor`) and save through stale-checked `repo_file_write`;
revisions, binaries, oversized files, and non-UTF-8 text stay on Pierre's
read-only `<File>`.
Writes are explicit only, via the disk save icon or Mod+S; blur and idle time
never save a draft. Focus/watcher refreshes keep the mounted editor and token
map in place, and a refresh that finishes after typing starts cannot replace
the draft (`ContentTab` loaded-source/dirty guards).
never save a draft. Unsaved drafts stay in memory when navigating around
Strand. Focus/watcher refreshes keep the mounted editor state in place, and a refresh that finishes
after typing starts cannot replace the draft (`ContentTab` loaded-source/dirty guards).
Mod+F searches the source with wrap-around match navigation and
virtualized-line scrolling (`FileSearchBar` + `searchFileText`).
- ☑ Preview tab — rendered view for renderable text files, tab only offered
Expand Down
47 changes: 36 additions & 11 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"doctor": "npx react-doctor@latest"
},
"dependencies": {
"@pierre/diffs": "^1.2.3",
"@pierre/diffs": "^1.3.3",
"@pierre/theme": "1.1.0",
"@pierre/trees": "1.0.0-beta.5",
"@tauri-apps/api": "^2",
Expand Down
8 changes: 7 additions & 1 deletion ui/src/lib/fileEditing.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';

import { canEditFileContent } from './fileEditing';
import { canEditFileContent, fileDraftKey } from './fileEditing';

describe('file content editing', () => {
it('keeps accepted working-tree text editable in every presentation', () => {
Expand All @@ -11,4 +11,10 @@ describe('file content editing', () => {
expect(canEditFileContent(true, 'abc123')).toBe(false);
expect(canEditFileContent(false, null)).toBe(false);
});

it('keys unsaved drafts only for working-tree files', () => {
expect(fileDraftKey('/repo', 'src/main.ts', null)).toBe('/repo\u0000src/main.ts');
expect(fileDraftKey('/repo', 'src/main.ts', 'abc123')).toBe(null);
expect(fileDraftKey(null, 'src/main.ts', null)).toBe(null);
});
});
5 changes: 5 additions & 0 deletions ui/src/lib/fileEditing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@
export function canEditFileContent(editable: boolean, revision: string | null): boolean {
return editable && revision === null;
}

export function fileDraftKey(repoPath: string | null, path: string, revision: string | null): string | null {
if (!repoPath || revision !== null) return null;
return `${repoPath}\u0000${path}`;
}
Loading
Loading