Skip to content

Ux issue 4#455

Open
clintjeff2 wants to merge 8 commits intodotandev:mainfrom
clintjeff2:ux-issue-4
Open

Ux issue 4#455
clintjeff2 wants to merge 8 commits intodotandev:mainfrom
clintjeff2:ux-issue-4

Conversation

@clintjeff2
Copy link
Contributor

PR: Persistent UI State Across Viewer Sessions (Issue #312)

Closes #312

Summary

When a user reopens a previously debugged transaction the output context is now
automatically restored: which sections were active last time is printed at the
start of the debug run, and any search queries typed into erst search are
saved for instant recall with erst search --recent.


Changes

internal/session/uistate.go (new)

Introduces UIStateStore, a lightweight SQLite-backed store at
~/.erst/ui_state.db that is completely separate from the session store to
avoid schema conflicts.

Two tables:

table purpose
tx_ui_state per-transaction list of visible output sections
recent_searches global ordered list of search queries (max 20, deduped)

Public API:

  • NewUIStateStore() (*UIStateStore, error)
  • SaveSectionState(ctx, txHash, []string) error
  • LoadSectionState(ctx, txHash) ([]string, error)
  • AppendRecentSearch(ctx, query string) error
  • RecentSearches(ctx, limit int) ([]string, error)
  • Close() error

internal/session/uistate_test.go (new)

Nine tests covering every behaviour path:

test what it checks
TestSaveSectionState round-trip save/load
TestLoadSectionState_NoEntry returns nil when no state stored
TestSaveSectionState_OverwritesPrevious second save replaces first
TestSaveSectionState_IndependentPerTxHash states do not bleed across hashes
TestAppendRecentSearch ordered newest-first
TestAppendRecentSearch_Deduplication re-adding a query bubbles it to top
TestAppendRecentSearch_TrimToMax never exceeds maxRecentSearches
TestRecentSearches_Empty empty slice on fresh store
TestAppendRecentSearch_EmptyQueryIgnored empty string is a no-op

internal/cmd/debug.go

  • Opens UIStateStore right after the transaction hash is resolved (best-effort;
    failure does not abort the debug run).
  • If a previous section list exists for this hash, prints:
    Restoring viewer state: last session showed [budget, events, logs] for this transaction.
    
  • Tracks whether the token-flow section produced output (hasTokenFlows).
  • After security analysis and token-flow rendering, calls
    uiStore.SaveSectionState with the computed section list.
  • New helper collectVisibleSections derives the list from the simulation
    response, security findings, and token-flow flag.

internal/cmd/search.go

  • New --recent flag: erst search --recent prints the last 10 search queries
    ordered newest-first and exits immediately.
  • After a successful search, every non-empty flag value (--error, --event,
    --tx) is persisted via UIStateStore.AppendRecentSearch (best-effort).

internal/cmd/session.go

  • erst session resume <id> now shows persisted viewer state at the end of its
    output when data is available:
    Viewer state: [budget, events, logs]
    Recent searches: transfer, mint
    

Test Results

=== RUN   TestSaveSectionState                    --- PASS (0.06s)
=== RUN   TestLoadSectionState_NoEntry            --- PASS (0.04s)
=== RUN   TestSaveSectionState_OverwritesPrevious --- PASS (0.07s)
=== RUN   TestSaveSectionState_IndependentPerTxHash --- PASS (0.07s)
=== RUN   TestAppendRecentSearch                  --- PASS (0.08s)
=== RUN   TestAppendRecentSearch_Deduplication    --- PASS (0.08s)
=== RUN   TestAppendRecentSearch_TrimToMax        --- PASS (0.46s)
=== RUN   TestRecentSearches_Empty                --- PASS (0.04s)
=== RUN   TestAppendRecentSearch_EmptyQueryIgnored --- PASS (0.04s)

ok  github.com/dotandev/hintents/internal/session  0.949s

Build: go build ./... exits 0 with no warnings.
Screenshot from 2026-02-24 02-42-16


Checklist

  • go build ./... — clean, 0 errors, 0 warnings
  • go test ./internal/session/... — 9/9 pass
  • No pre-existing tests broken (only the unrelated TestSearchUnicode_Mixed failure persists)
  • Section state is stored per transaction hash
  • Recent searches are deduplicated and trimmed to 20 entries
  • All UIState operations are best-effort (never abort the parent command)
  • erst search --recent shows past queries without requiring a new search
  • erst session resume surfaces saved section state and recent searches

@dotandev
Copy link
Owner

resolve conflicts

Copilot AI review requested due to automatic review settings February 25, 2026 14:11
@gitguardian
Copy link

gitguardian bot commented Feb 25, 2026

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

Since your pull request originates from a forked repository, GitGuardian is not able to associate the secrets uncovered with secret incidents on your GitGuardian dashboard.
Skipping this check run and merging your pull request will create secret incidents on your GitGuardian dashboard.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
27597558 Triggered Generic Private Key 3e9daa6 SECURITY_REMEDIATION_PR463.md View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements persistent “viewer” UI state across runs by adding a dedicated SQLite-backed store for per-transaction visible sections and globally recent search queries, then wiring it into debug, search, and session resume.

Changes:

  • Add internal/session/UIStateStore (SQLite at ~/.erst/ui_state.db) to persist section visibility per tx hash and deduped recent searches.
  • Integrate UI state restore/persist into erst debug, and surface it in erst session resume.
  • Add erst search --recent and persist search terms after successful searches.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
internal/session/uistate.go New SQLite-backed persistence layer for per-tx visible sections and recent searches
internal/session/uistate_test.go Unit tests covering UI state persistence, dedupe, and trimming behavior
internal/cmd/debug.go Loads/restores prior visible sections; persists visible sections after analysis
internal/cmd/search.go Adds --recent output and persists search terms after successful searches
internal/cmd/session.go Displays persisted viewer state + recent searches on session resume
Comments suppressed due to low confidence (1)

internal/cmd/search.go:84

  • errors.WrapValidationError(...) is used in this file, but github.com/dotandev/hintents/internal/errors is no longer imported, so this won’t compile. Re-add the internal/errors import (or update the error handling to not reference errors).
		store, err := db.InitDB()
		if err != nil {
			return errors.WrapValidationError(fmt.Sprintf("failed to initialize session database: %v", err))
		}

		params := db.SearchParams{
			TxHash:     searchTxFlag,
			ErrorRegex: searchErrorFlag,
			EventRegex: searchEventFlag,
			Limit:      searchLimitFlag,
		}

		sessions, err := store.SearchSessions(params)
		if err != nil {
			return errors.WrapValidationError(fmt.Sprintf("search failed: %v", err))
		}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

LIMIT ?
)
`, maxRecentSearches)
return err
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

AppendRecentSearch returns the raw error from the trimming DELETE without wrapping, which is inconsistent with the other methods in this file (they add context via fmt.Errorf("...: %w", err)). Wrap/annotate the error here as well so callers get actionable context when trimming fails.

Suggested change
return err
if err != nil {
return fmt.Errorf("failed to trim recent searches: %w", err)
}
return nil

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +57
if searchRecentFlag {
uiStore, err := session.NewUIStateStore()
if err != nil {
return fmt.Errorf("failed to open viewer state: %w", err)
}
defer uiStore.Close()
queries, err := uiStore.RecentSearches(cmd.Context(), 10)
if err != nil {
return fmt.Errorf("failed to load recent searches: %w", err)
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The PR description/checklist says UIState operations are “best-effort (never abort the parent command)”, but --recent currently returns an error if the UI state DB can’t be opened/read. Consider making erst search --recent degrade gracefully (e.g., print a warning / “No recent searches.” and exit 0) to match the stated best-effort behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +55
return nil, err
}
_ = os.Chmod(dbPath, 0600)
return s, nil
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

os.Chmod(dbPath, 0600) errors are ignored. If chmod fails (e.g., on Windows or restrictive filesystems), the DB may be left with broader permissions than intended. Handle and surface this failure (at least log a warning, similar to internal/session/store.go) so users aren’t silently left with an insecure file mode.

Copilot uses AI. Check for mistakes.
@clintjeff2
Copy link
Contributor Author

@dotandev CONFLICTS FIXED

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.

[UX] Persistent UI state across viewer sessions #104

3 participants