Edit any session by index: update_session_at#7
Open
yncyrydybyl wants to merge 19 commits into
Open
Conversation
The workspace tests were catching RuntimeError but the new error type is UninitializedLedgerError (introduced in commit 263a93e). Update the exception handling to match the current API.
…al log and plan/plan hints.
faff-core has had Python (PyO3) and WASM (wasm-bindgen) bindings, but no ergonomic path for other languages. This adds a third workspace member that exposes the workspace through UniFFI, enabling uniffi-bindgen-go, uniffi-bindgen-cs, uniffi-bindgen-swift, etc. to consume faff-core in-process without shelling out to faff-cli. The first foreign consumer is faff-tui (a Bubbletea TUI in Go), which imports the generated bindings and calls FaffWorkspace methods directly. ## Surface The UDL exposes: - FaffWorkspace constructor that wraps Workspace::new() on a tokio runtime owned by the binding (one runtime per workspace, vs bindings-python's per-call Runtime::new()) - today() / timezone() — workspace metadata - get_log(date) — read a daily log as a flat dictionary; time fields are RFC3339 strings to avoid teaching UniFFI about chrono::DateTime<Tz> - start_session(...) / stop_current_session() — write paths - get_roles/impacts/modes/subjects/trackers/session_hints(date) — vocabulary queries from PlanManager - recent_prototypes(days) — a binding-side convenience that walks the last N days of logs and returns deduplicated session shapes ranked by usage count then recency. This replaces the old plan-level Intent concept (removed in test_plan_deserialization_ignores_intents) with a history-derived picker source. Lives here rather than in core because it's a UI-shaped concern. ## Pinned versions - uniffi-rs 0.31.0 (workspace dep) - targets uniffi-bindgen-go v0.7.0+v0.31.0 on the consumer side ## Build cargo build -p faff-core-uniffi (debug, ~70MB cdylib) cargo build --release -p faff-core-uniffi (release, ~7.6MB cdylib) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two small immutable builders that LogManager will use to support in-flight session edits in the next commit: - Session::with_fields(title, role, impact, mode, subject, trackers, note) mirrors with_end and with_reflection — replaces the semantic field cluster wholesale, preserves start/end and any reflection. - Log::update_active_session(new_session) returns a new Log with the active (last, end=None) session replaced. Errors if no session is currently active. - LogError::NoActiveSession added for the new failure mode. No behavior change to existing call paths. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds the manager-level method that wires Log::update_active_session into the workspace plumbing: - Validates that any tracker passed is present in today's plan, mirroring start_session's existing tracker check. - Loads today's log, finds the active session, applies with_fields, and persists via the standard write_log path. - Errors cleanly if there's no active session (either empty timeline or the last session is closed). Six new tests in mod tests: - replaces_fields: happy path, all fields swap - errors_without_active: empty log → error - errors_when_last_session_closed: closed timeline → error - rejects_unknown_trackers: validation guard - preserves_other_sessions: neighbor session untouched - preserves_reflection: reflection_score/text survive an edit Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds the Stage 2 write path needed by faff-tui's edit form: - New UDL method on FaffWorkspace, mirroring the Rust signature exactly: update_active_session(title?, role?, impact?, mode?, subject?, trackers, note?) - New FaffError variant: NoActiveSession. Mapped from anyhow on the best-effort string-match path that the existing Other variant uses. - The wrapper goes through the binding's owned tokio runtime — same pattern as start_session / stop_current_session. The first foreign consumer (yncyrydybyl/faff-tui) calls UpdateActiveSession from a 7-field edit form opened with `e` on the dashboard. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extends the edit API beyond the currently-active session. Callers can now
edit the semantic fields of any session in any log by (date, index),
preserving start/end times and any reflection. Mirrors the existing
update_active_session pathway and shares its tracker validation.
Core
----
- Session::with_fields already preserved times, reused as-is.
- Log::update_session_at(index, new_session) — immutable index-based
timeline replacement with bounds checking.
- LogError::IndexOutOfRange { index, len } — new variant for the above.
- LogManager::update_session_at(date, index, fields...) — resolves the
plan for the target date (not today), validates trackers, writes the
updated log.
Bindings (UniFFI)
-----------------
- FaffWorkspace::update_session_at exposed via faff_core.udl and
bindings-uniffi/src/lib.rs. Takes date (YYYY-MM-DD) + u32 index.
- Errors map through anyhow to FaffError::Other, same as
update_active_session.
Tests
-----
- test_update_session_at_replaces_fields — verifies field replacement
preserves start, end, and leaves other sessions untouched.
- test_update_session_at_invalid_index — out-of-range index errors
with "out of range" in the message.
- test_update_session_at_preserves_reflection — reflection survives
semantic edits.
- All 169 existing tests still pass.
First consumer is faff-tui's edit form, which now targets any session
by cursor position rather than only the active one.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…_session_at Two gaps identified in test-expert review: 1. Tracker validation rejection was untested — the check exists in update_session_at (mirrors update_active_session) but no test exercised the failure path. Added a test that passes "bogus:tracker" and asserts the error contains "not found". 2. Cross-date editing was untested — all previous tests used ws.today(), but the whole point of update_session_at vs update_active_session is editing past sessions. Added a test that writes a log for yesterday, edits a session in it, and asserts: the edit lands in yesterday's log (times preserved), and today's log stays empty. 5/5 update_session_at tests now pass (3 existing + 2 new). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.
Summary
Extends the edit API beyond the currently-active session. Callers can now edit the semantic fields of any session in any log by
(date, index), preserving start/end times and any reflection.First consumer is yncyrydybyl/faff-tui, whose edit form now targets any session by cursor position on the dashboard rather than only the currently-active one.
What's new
Core (
core/src/)Log::update_session_at(index, new_session)— immutable index-based timeline replacement with bounds checking.LogError::IndexOutOfRange { index, len }— new variant.LogManager::update_session_at(date, index, title, role, impact, mode, subject, trackers, note)— resolves the plan for the target date (not today), validates trackers, writes the updated log. Uses the existingSession::with_fieldshelper so times and reflection survive untouched.Bindings (
bindings-uniffi/)FaffWorkspace::update_session_atexposed via UDL +lib.rs. Takesdate(YYYY-MM-DD string) +u32 index.FaffError::Other, consistent withupdate_active_session.Tests
test_update_session_at_replaces_fields— field replacement preserves start/end, leaves other sessions untouched.test_update_session_at_invalid_index— out-of-range index errors cleanly.test_update_session_at_preserves_reflection— reflection survives semantic edits.Test plan
cargo test -p faff-core— 169/169 pass including 3 newcargo build -p faff-core-uniffi— clean debug + releasemake regenin faff-tui picks upUpdateSessionAtin generated Go bindingsj/kcursor +ekey successfully edits a past session and persists to the TOML file with time fields preserved🤖 Generated with Claude Code