uistate

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 13, 2026 License: MIT Imports: 14 Imported by: 0

README

Durable desktop reading state

uistate owns the Inbox's bookkeeping, review walkthrough drafts, each page's filter and saved Pull requests tabs, and whether the first-run tour was seen in the shared private application database (internal/appdb). It imports no legacy browser storage. Original coding-session transcripts and submitted GitHub reviews are outside this store.

Document Contents
ui/inbox local day; per-PR seen/cleared records; time-based snoozes; head-based snoozes; archives (archived, optional)
ui/filters each page's filter query and the saved Pull requests tabs ({pages: {<page>: query}, tabs: [{id, name, view, query}]})
ui/tour whether this computer has finished or skipped the app's first-run tour ({seen})
ui/walkthrough per repository/PR/review-version seen stops, draft comments per stop, and overall comment
ui/review per repository/PR, the comments and summary of the reader's own review until it is sent, and the reviews sent from the app

A filter page is inbox, inbox:<inbox id> or pulls, at most 64 of them, and a page without a filter is left out (the app drops the least recently changed beyond 60). A query is one line of at most 500 characters; the daemon never parses it. A tab has a 32-hex id (unique), a trimmed name of 1 to 40 characters, a view (open or history, Closed) and a query; at most 20 tabs. A stored filters document in another shape reads as none (at its revision), and the next save replaces it.

A walkthrough key is owner/repository#number@vversion (optionally prefixed with an Enterprise host). The latest 40 saved review versions are retained. A review key is owner/repository#number, and the latest 200 pull requests' reviews are retained. A review is {comments, note}: at most 100 comments, each {id, target: {kind, id, title}, body, place, head, created} with a unique id (1–64 of A-Za-z0-9_-), kind general, section, risk or file (all but general with an id), a non-blank body, place null or {path, line, side, start_line?} (side RIGHT or LEFT, start_line before line), and head empty or the hex commit the place is on. An optional sent holds at most 20 reviews sent from the app, each {url, event, at, note, comments} with an https:// URL, event APPROVE, COMMENT or REQUEST_CHANGES, and at most 100 {title, where, body} comments. The save timestamp is advanced monotonically within the transaction so an unchanged or backwards wall clock cannot prune the draft just saved. Inbox rollover/clearing semantics remain in the frontend queue model, which knows whether its GitHub index read is complete and which actions clear work.

HTTP

The daemon mounts this provider inside its Host, session/bearer, Origin and JSON-content checks. These routes grant no filesystem access.

  • GET /api/ui-state/inbox
  • PUT /api/ui-state/inbox
  • GET /api/ui-state/filters
  • PUT /api/ui-state/filters
  • GET /api/ui-state/tour
  • PUT /api/ui-state/tour
  • GET /api/ui-state/walkthrough?key=<URL-encoded review key>
  • PUT /api/ui-state/walkthrough?key=<URL-encoded review key>
  • GET /api/ui-state/review?key=<URL-encoded pull request key>
  • PUT /api/ui-state/review?key=<URL-encoded pull request key>

Reads return {revision, value}; an absent record is {revision: 0, value: null}. Reads never create a database. Writes require {revision, mutation_id, value} and return the saved record. A stale revision is 409 stale_ui_state with the current revision/value. Revisions are per record, so editing another review cannot stale this one. All walkthrough and review retention and revision changes commit atomically through appdb.Update.

Each window's record client has a random identifier and increasing batch sequence, encoded as mutation_id: "<client>:<sequence>". The store retains the highest committed sequence for every client while the record is retained, without evicting acknowledgements after keystrokes from another window. Retries at or below that sequence acknowledge the latest record before revision comparison, without replacing it. Client high-water marks are private bookkeeping and are not in HTTP responses. The window keeps the same client and sequence while retrying an unacknowledged batch.

Requests are limited to 512 KiB; values to 384 KiB, with bounded collection, key and note sizes. Invalid keys/types/values are 400. An unreadable stored record or database is 500 with a fixed message; it is never overwritten with defaults. Required fields and string/map values reject null and missing values. Every retained walkthrough or review record is validated before reading, writing or pruning; a corrupt sibling fails the operation and leaves original bytes untouched. Callback errors and stale writes leave prior bytes untouched.

Client behavior and verification

oss/app/src/ui-state.tsx owns per-window shared snapshots, asynchronous hydration, sequential pending changes and explicit Retry after a failed read/write. Changes made during a read are replayed on what the daemon returns. An in-flight refresh cannot race a save. The walkthrough and Ask panel share one record; append/Undo replay against current text, preserving unrelated comments and later edits. Focus refreshes an idle record from another window.

Tests cover database reopening, concurrent CAS and independent reviews, retention with repeated/backwards clocks, corruption preservation, input limits, authentication/Origin/namespace boundaries and a new daemon origin. Frontend tests cover delayed reads, concurrent edits, failed reads/writes and commit-then-response-loss retries with intervening edits. Browser tests clear localStorage and open fresh contexts to verify snoozes and draft comments.

Run in Docker from the repository root:

just oss-go test -race ./internal/uistate ./internal/uiserver
just local_app::check
just local_app::e2e

Documentation

Overview

Package uistate keeps the person's inbox bookkeeping and review drafts in the application database, independently of a browser's loopback origin.

Index

Constants

View Source
const KeepReviews = 200

KeepReviews is how many pull requests' pending reviews are retained.

View Source
const KeepSent = 20

KeepSent is how many sent reviews a pull request's record keeps.

View Source
const KeepWalkthroughs = 40
View Source
const MaxReviewComments = 100

MaxReviewComments is how many comments one pending review holds.

View Source
const MaxTabs = 20

MaxTabs is how many saved Pull requests tabs the filters document holds.

Variables

View Source
var ErrInvalid = errors.New("invalid saved UI state")
View Source
var ErrUnreadable = errors.New("saved UI state is unreadable")

Functions

This section is empty.

Types

type Record

type Record struct {
	Revision int               `json:"revision"`
	Value    json.RawMessage   `json:"value"`
	Clients  map[string]uint64 `json:"clients"`
}

type StaleError

type StaleError struct{ Current Record }

func (*StaleError) Error

func (e *StaleError) Error() string

type Store

type Store struct {
	Path string
	Now  func() time.Time
}

func (*Store) Handler

func (s *Store) Handler() http.Handler

Handler is mounted only behind the daemon's Host, authentication and CSRF boundary.

func (*Store) Read

func (s *Store) Read(kind, key string) (Record, error)

func (*Store) Replace

func (s *Store) Replace(kind, key string, revision int, mutation string, value json.RawMessage) (Record, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL