Skip to content

feat(app): --readonly flag blocks every write op with a READONLY badge#7

Open
posquit0 wants to merge 2 commits into
mainfrom
feat/qw2-readonly-flag
Open

feat(app): --readonly flag blocks every write op with a READONLY badge#7
posquit0 wants to merge 2 commits into
mainfrom
feat/qw2-readonly-flag

Conversation

@posquit0

@posquit0 posquit0 commented Jul 5, 2026

Copy link
Copy Markdown
Member

Summary

  • New global --readonly CLI flag threads a ReadOnly bool through cmd/otaapp.Deps and gates every UI write-flow entry point in the App Shell reducer (edit forms, all status pickers, palette write commands like :reset-password / :unlock / :reset-mfa, and any palette route targeting a *Edit screen).
  • Blocked ops surface a single canonical toast (read-only mode — write ops disabled) via the existing toastCmdInfo path.
  • Chrome stamps a Warning-toned [READONLY: on] badge for the session so operators see the guardrail at a glance.
  • Read flows (list, detail, search, filter, logs tail) stay 100% functional — pure UI gate, no business-logic changes.

Implementation notes

  • Model.blockedByReadOnly() is the single decision point every gate calls; returns (bool, tea.Cmd) so the pattern stays uniform across handlers.
  • isWriteScreen(Screen) covers the palette / SwitchScreenMsg paths for :edit, :group-edit, etc.
  • Badge Value is "on" because bare Value collapses to a Muted [KEY] rendering and drops the Tone — see shared.formatStatusBadge.

Test plan

  • go build ./... clean
  • go test ./... -race -count=1 clean
  • internal/app/readonly_test.go regression: OpenUserEditMsg and OpenStatusPickerMsg blocked with toast, palette :reset-password blocked, chrome View stamps the badge, default (ReadOnly=false) omits the badge.
  • cmd/ota/readonly_test.go smoke: run --readonly --version exits 0 (flag registered, no "flag provided but not defined").

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a --readonly CLI flag and a corresponding read-only mode for the TUI application. When enabled, this mode blocks all write operations—such as edit forms, status pickers, and destructive palette commands—and displays a canonical toast message while showing a 'READONLY' warning badge in the status row. The feedback identifies a potential bypass where shared.OpenScreenMsg lacks the isWriteScreen read-only check, which could allow navigation to write screens in read-only mode.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread internal/app/app.go
Comment on lines 784 to +790
case SwitchScreenMsg:
if s, ok := screenFromName(msg.Target); ok {
if isWriteScreen(s) {
if blocked, cmd := m.blockedByReadOnly(); blocked {
return m, cmd
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

While SwitchScreenMsg and ScreenChangeMsg are properly guarded with isWriteScreen checks, shared.OpenScreenMsg (handled right below at line 798) also resolves targets via screenFromName (which can return write screens like ScreenUserEdit or ScreenGroupEdit) but lacks the isWriteScreen read-only check. To prevent potential bypasses where a cross-screen drill-down could navigate to a write screen in read-only mode, please add the same guard to shared.OpenScreenMsg.

posquit0 added 2 commits July 5, 2026 23:50
QW-2 — new global --readonly CLI flag threads a ReadOnly bool through
cmd/ota → app.Deps and gates every UI write-flow entry point in the
App Shell reducer (edit forms, status pickers, palette write commands
like :reset-password / :unlock / :reset-mfa, and any palette route
targeting a *Edit screen). Blocked ops surface a single canonical
toast ("read-only mode — write ops disabled") and the chrome stamps
a Warning-toned READONLY badge for the session so operators see the
guardrail at a glance.

Read flows (list, detail, search, filter, logs tail) stay 100%
functional — this is a pure UI gate, no business-logic changes.
The OpenScreenMsg cross-screen drill-down path resolved targets via
screenFromName, which happily returns write screens like ScreenUserEdit
and ScreenGroupEdit. Without the isWriteScreen / blockedByReadOnly guard
that SwitchScreenMsg and ScreenChangeMsg already apply, a drill-down
targeting "user-edit" bypassed --readonly and pushed the edit form onto
the nav stack. Apply the same gate here and add a regression test that
dispatches OpenScreenMsg{Target:"user-edit"} under ReadOnly:true and
asserts the active screen does not change plus a read-only toast fires.
@posquit0 posquit0 force-pushed the feat/qw2-readonly-flag branch from f794d04 to 607e499 Compare July 5, 2026 14:50
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.

1 participant