Addresses the PR #98 review:
- HIGH symlink escape: resolve() did only a lexical starts_with, so a
base/autosave symlink -> /tmp/out let state.write("autosave/x") write
outside the state dir. Now every existing component the key adds under
base is lstat'd and a symlink (live OR broken) is rejected; base itself
may still be a symlink (dotfile-managed ~/.local/state). Unix symlink
escape test added (live + broken + plain-subdir-ok).
- MEDIUM integration-test state leak: the state/history dir wiring moved
out of EditorState::new() into EditorState::install_state_dirs(),
called only by the real entry points (editor::run, run_daemon). Unit
AND integration tests construct EditorState directly, so they never
configure a real dir -> default-on recentf/saveplace write nothing to
~/.local/state/pmacs during cargo test. The inertness test now asserts
a bare new() leaves StateDir unconfigured (direct proof).
- MEDIUM saveplace never recorded view_top: exposed the missing
pmacs.editor.view_top() getter (set_view_top existed but no getter, so
the Lua stored 0). saveplace now records+restores the viewport;
acceptance asserts view_top restores, not just the cursor byte.
- MEDIUM/LOW relative XDG_STATE_HOME / PMACS_STATE_HOME: a relative
value rooted state at a cwd-relative pmacs/... (same footgun class as
the empty case). Both are now required absolute; relative values are
ignored (XDG falls through to HOME). Test added.
- LOW trailing blank line at recentf.lua EOF (git diff --check).
Gates: fmt + workspace clippy clean; lib 1483; crdt 1654; persistence 5;
m4 90; m8_1/m8_2 daemon 10/15; query-replace/completion/listview/overlay/
cua green; GPU 58; git diff --check clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>