Commit Graph

89 Commits

Author SHA1 Message Date
Edward Langley 271f34978b Merge gh/main (666de199) into fix/fy9o-reopened-editor-reseeded
This head already carries #272 (p6v6), merged at d9c65181.

Conflicts:
- resolve.rs advanced: main's `reopened_at` (decline an aggregate or a
  formula's result) now takes this branch's `writes` and answers the
  decline arm first, then `awaiting_seed` or `opened_on(state, ..)`, as
  the #282 review prescribed for this combination.
- context/test-evidence/{clients,command}.md: both sides' entries kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 23:07:51 -07:00
Ed Langley eb9ec7e98b Merge pull request #277 from fiddlerwoaroof/fix/enz6-advance-onto-fold
No editor re-opens on a cell i declines: a fold or a formula's result (improvise-enz6)
2026-09-23 22:41:16 -07:00
Ed Langley eade33c94e Merge pull request #264 from fiddlerwoaroof/fix/uln5-help-black-holes
Mark the help rows a browser cannot run, and guard it from one list (improvise-uln5)
2026-09-23 22:40:34 -07:00
Edward Langley f4c0feec2c Close the entry half of improvise-enz6: no editor re-opens on a cell i declines
EnterEditAtCursor asks core::uneditable, the commit door's own question,
now shared with committable_cell_key. The browser's advance asks the
projected id at the landing cell; lockstep pins both ends on a fold and
on a formula's cell.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 17:49:44 -07:00
Edward Langley 9872cc936f Every cell-editor opener awaits its seed while a write is unanswered (improvise-p6v6)
mdfc put the "any unanswered write" check inside `advanced` alone, so an
editor opened with i/a (`edit_or_drill`) still seeded from the cache the
write had made stale: `i 9 Tab Esc i 5 Enter`, typed faster than the
answer, stored 15 in the browser where the TUI stores 195.

The in-flight half now lives in `opened_on`, so no opener can forget it.
`advanced` keeps the "this frame writes" half, since the commit being
resolved is not in flight yet; dropping it fails the 3tt4/3uzg tests.

Stacked on #227 (fix/fy9o-reopened-editor-reseeded, de4a7d78).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 17:33:33 -07:00
Edward Langley cf5c6fc711 Mark the help rows a browser cannot run, and guard it from one list
improvise-uln5. The browser painted every help-overlay row plainly,
including six whose keys go upstream to a host that answers nothing:
/, n, H, z, Enter, and search mode's Enter/Esc. Every ex-command row was
painted plainly too, although `:` opens a command line the browser
cannot run. A dead key is worse than a gap: the keys typed after it land
on live bindings (improvise-koh6).

- resolve::NEEDS_THE_HOST is now the one list of names this client has
  no arm for. KeyRow keeps the command it was built for.
- The browser's RenderHelpRow draws a listed row, and every ex row while
  the command line is stranded, as `dim` with "(not in the browser yet)".
  It marks rather than omits: both front ends keep drawing the same rows,
  the feature stays findable, and web/ needs no change.
- the_command_names_that_still_need_the_host_are_these now drives that
  list rather than a table of its own.
- every_help_row_the_browser_cannot_run_is_one_it_marks holds it from
  the other side: silent help rows == listed rows, and no hint line
  names one.
- every_advertised_command_answers_in_its_own_mode no longer restates
  the black holes.

Red: a_help_row_the_browser_cannot_run_is_drawn_marked at 202e7eac.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 17:22:48 -07:00
Edward Langley 3fcd407b08 A reopened editor awaits any unanswered write, settles only while editing, and states its awaiting flag (improvise-mdfc, improvise-4cb5, improvise-mgis)
improvise-mdfc: an untouched commit seeded the reopened editor from the
cache even while an earlier write from this client was unanswered, so
Tab Tab at the rightmost column stored 505 where the TUI stores 5095.
`advanced` now awaits when this frame writes or any in-flight frame is
awaited. The lockstep test sends three Tabs before answering any.

improvise-4cb5: `settle_awaited_seed` also requires an editing mode, so
an Esc before the answer no longer leaves the seed in Normal's buffer.

improvise-mgis: `OpenedEditor::awaiting` loses `serde(default)`; a frame
missing it fails to parse as the missing field (protocol.md §3, 1wl0).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 16:59:23 -07:00
Edward Langley eb146bccab Pin the browser's Tab-commit advance and the tile cursor's upper clamp
improvise-rq3f: two resolver faults passed the whole workspace. Two unit
tests in resolve.rs now fail under each, and only under its own fault.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 11:29:13 -07:00
Edward Langley baabe1a9b1 Evidence and the argument for seeding a reopened editor from the host's answer (improvise-3uzg, improvise-xbgm)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 10:59:41 -07:00
Edward Langley 1d2032ef82 Merge remote-tracking branch 'gh/main' into fix/fy9o-reopened-editor-reseeded
# Conflicts:
#	context/test-evidence/clients.md
#	context/test-evidence/command.md
#	crates/improvise-wasm-client/tests/lockstep.rs
2026-09-23 10:54:59 -07:00
Edward Langley 96ecfd6018 After a write, the reopened editor takes its seed from the host's answer (improvise-3uzg)
landed_seed assumed the client's cache was stale only at the cell the frame
wrote. Under prune_empty a write can take its row away and renumber the rest,
so the advance landed on a position the cache still filled with another
record. The advance after a write now opens the editor awaiting its seed, and
the client settles it from the grid it holds once nothing it sent is
unanswered. A seed never overwrites typing that arrived first.

This also removes CellValue::edit_text from the client bundle, so the
nontrapping-float-to-int wasm-opt flag a093d126 added is no longer needed and
is reverted (improvise-xbgm).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 10:53:59 -07:00
Edward Langley 463bef149d Merge commit '5932ad749f0df01d4321cc961f57a488ab637dde' into wave/km71-edit-formula
# Conflicts:
#	crates/improvise-protocol/src/lib.rs
2026-09-23 08:51:45 -07:00
Edward Langley 85c483bd24 Tab at the rightmost column reopens on the value just written; pin the +cat clear (improvise-3tt4, improvise-5wai)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 08:38:25 -07:00
Edward Langley 5960fe8f0a Merge gh/main (e406edc8) into fix/oplp-tile-cursor
Clean merge, no conflicts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 08:35:24 -07:00
Edward Langley e5b18d3499 Merge remote-tracking branch 'gh/main' into wave/km71-edit-formula
# Conflicts:
#	context/test-evidence/command.md
#	crates/improvise-protocol/src/reduce.rs
2026-09-23 08:32:01 -07:00
Edward Langley a1b23c0595 Merge remote-tracking branch 'gh/main' into fix/fy9o-reopened-editor-reseeded
# Conflicts:
#	context/test-evidence/clients.md
#	context/test-evidence/command.md
2026-09-23 08:26:51 -07:00
Edward Langley 0051e57341 Pin the touched reset on the editor an advance reopens (improvise-465t)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 08:20:36 -07:00
Edward Langley c6c2de96b2 Merge remote-tracking branch 'gh/main' into fix/ixxx-untouched-commit
# Conflicts:
#	context/test-evidence/clients.md
#	context/test-evidence/command.md
2026-09-23 08:13:40 -07:00
Edward Langley 2383c37f2f Merge remote-tracking branch 'gh/main' into fix/oplp-tile-cursor
# Conflicts:
#	context/test-evidence/clients.md
2026-09-23 08:10:09 -07:00
Edward Langley 27cbe2c1dd Tile select opens on the first regular category, not _Index
improvise-oplp: EnterTileSelect and the browser's resolver both opened the
tile cursor at 0, which is _Index, so `T Enter` or `T r` put _Index alone
on Row and emptied a pivot grid; the browser worker then persisted it.

The rule is improvise_protocol::opening_tile, over &[TileEntry], called by
both ends. TileEntry gains `regular`, filled host-side from CategoryKind in
project::tiles_of, because a thin client cannot tell _Index from a user
category without sniffing names. _Index/_Dim stay tileable (#192).

A lockstep case presses `T Enter` on both ends and pins which category
moved; it was red at base (_Index on Row), and fails for a TUI-only revert
and for a `regular` filled true everywhere. contract.spec.js counted three
ArrowRights from tile 0 and now asserts `T` alone selects Product.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 08:08:54 -07:00
Edward Langley ca19b7686d A reopened cell editor shows the cell it landed on (improvise-fy9o)
The cell-editing commits own their buffer clear at their head instead of
the keymap welding one after them, so the re-seed survives and Enter,
Enter writes nothing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 07:53:18 -07:00
Edward Langley a8737f3fa5 One page rule for both ends; the help overlay shows how to page
improvise-5xbq: resolve::page_rows is now the one home of the page-scroll
rule; the TUI's Move calls it instead of repeating visible_rows * 3 / 4.
A lockstep case pages (PgDn/PgUp, Ctrl-d/Ctrl-u) on a non-square window,
8 rows by 3 columns, and pins the TUI's landing rows as well as comparing
host and client, since one home moves both ends together. Both faults
the ticket injected (page_rows = window_rows / 2; paging by window_cols)
now fail at the first PgDn, before and after the move to one home.

improvise-1c5m: the help overlay draws the mode's own hint (hint_line,
the string #hint carries) in a sticky footer, so a narrow window whose
app bar wraps under #help still says how to page. Playwright case at
600px, red at base, and red again with the footer made non-sticky.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-23 07:41:03 -07:00
Edward Langley 77f059c852 Merge remote-tracking branch 'gh/main' into wave/km71-edit-formula
# Conflicts:
#	context/test-evidence/clients.md
#	context/test-evidence/command.md
2026-09-22 23:43:39 -07:00
Edward Langley a91db1d857 improvise-km71: edit the formula under the cursor, in place
Enter or e in the formula panel opens FormulaEdit on the formula under the
cursor, seeded with its text. The editor names the formula by a FormulaId
captured when it opens, and commits through a new ReplaceFormula effect and
ModelCommand. The edit keeps the formula's position, renames it in place when
the target changes, and refuses out loud if the formula was removed or
rewritten meanwhile, if the new target belongs to another formula, or if the
text does not parse. A refusal leaves the editor open over the user's text.

FormulaCache now carries each formula's id, so the browser composes the same
edit, and a formula-row click opens the editor on the clicked row.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 23:42:33 -07:00
Edward Langley 3b8b186f67 Pin that append and pop mark the editor, and only the editor's buffer does (improvise-2054)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 23:31:33 -07:00
Edward Langley 0f3d06afa2 WIP improvise-km71: edit a formula from the panel
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 23:31:24 -07:00
Edward Langley fa8c8b61a2 An edited editor always writes, and the stale-seed guards are pinned (improvise-2054, improvise-wv3h)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 23:14:50 -07:00
Edward Langley 7407abbca6 Merge remote-tracking branch 'gh/main' into fix/ixxx-untouched-commit
# Conflicts:
#	context/test-evidence/clients.md
#	context/test-evidence/command.md
#	crates/improvise-command/src/command/cmd/commit.rs
2026-09-22 23:11:06 -07:00
Edward Langley 9512a2db1f The grid-move walk stands far from every edge, so it sees magnitude (5kdr)
21x21 grid, 8x8 window, start (10,10): moves land at 10+d, page-scroll at
10+page_rows(8)*d, jumps at 0/20. grid_move's page rule is page_rows(),
which the test reads rather than restating. Doubled move-selection, a
one-row scroll-rows and a page-size-blind page-scroll each fail.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 22:39:36 -07:00
Edward Langley 66398f163f Merge branch 'fix/help-keys-checks' into fix/w7qd-ssoe-help-guards 2026-09-22 22:37:19 -07:00
Edward Langley 7628770d79 Merge remote-tracking branch 'gh/main' into fix/ixxx-untouched-commit
# Conflicts:
#	context/test-evidence/command.md
2026-09-22 22:31:19 -07:00
Edward Langley 856122604a rustfmt (improvise-ixxx)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 22:30:26 -07:00
Edward Langley 95bd593dbe Merge remote-tracking branch 'gh/main' into fix/help-keys-checks
# Conflicts:
#	context/test-evidence/clients.md
#	context/test-evidence/protocol.md
2026-09-22 22:30:19 -07:00
Edward Langley c8db3ee2dd WIP: an untouched cell edit writes nothing (improvise-ixxx)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 22:20:51 -07:00
Edward Langley 1c4a27243d Every grid move Normal binds must land where it says (w7qd)
scroll-rows and page-scroll derive their landing from their argument as
move-selection does; any bound command that moves the cursor with no
stated landing fails; the numeric floor is gone. Reversed Ctrl-d/u and
PgUp/PgDn, and a dropped jump table with $ sent to column 0, both fail.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 22:07:02 -07:00
Edward Langley bb1a05674c improvise-exdg: a records coordinate cell projects CellId::Coordinate
A drawn coordinate cell projected id: None, which ADR 002 and gotcha 42
define as "no projected cell". Absence was encoding a permission (the
host's). It is now its own kind, carrying no operand because no client
composes a coordinate rename. render.rs::editable is an exhaustive match
so the next variant must be classified. Paste, clear and edit are
unchanged: every write path still matches Record alone. The test that
pinned the bug (a_coordinate_column_is_projected_with_no_identity) is
replaced by one that enumerates the whole records grid.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 21:54:11 -07:00
Edward Langley f8345ea8c6 A keys row can only be looked up, and an ex row can name no key (v29m)
HelpLine::Keys carries a KeyRow whose fields are private to a module
whose only constructor looks the keys up; a builder that types one is a
compile error. ex! asserts at compile time that every word after the
command name is a placeholder, so ':q  or press Q' does not build.
':add-items <cat> a b c ...' is now ':add-items <cat> <item> ...'.
Renderers read the row through accessors (src/ui/help.rs, render.rs).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 21:43:18 -07:00
Edward Langley e6d7d13e90 The advertised-key guard judges by what the fold moved, off every edge (6562)
heard() compared command types, so a clamped move counted as acting. It
now folds the outcome and compares the view slice (status and disarm
aside); a Model command counts as sent. The fixture is a 3x3 grid with
the cursor at (1,1), three tiles with the cursor at 1, help page 1.
Direction is out of reach for any did-it-move check, so every grid key
is also pressed from the centre and must land where its binding says.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 21:40:17 -07:00
Edward Langley c5e72e889f Merge remote-tracking branch 'gh/main' into fix/help-keys-checks
# Conflicts:
#	context/test-evidence/protocol.md
2026-09-22 21:37:17 -07:00
Edward Langley 949810cd30 Merge remote-tracking branch 'gh/main' into fix/help-keys-checks 2026-09-22 21:19:50 -07:00
Edward Langley 3d03d9ff8f fmt, and the comment cap
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 21:19:44 -07:00
Edward Langley 740511e0f9 Help rows name no key by hand, and the guard hears what advertised keys do
wk18: gg/yy/ZZ and the page tip are keymap lookups (prefixed sequences
included); ex-command rows can only be built by ex!, whose colon is its
own. help_bindings() records every row the pages ask for, from the same
builders.

3t5x: every_advertised_command_answers_in_its_own_mode walks the hint
line AND the help pages, judges each key by what moved (a lone status is
Declines, a disarm alone is Silent), and names every key that does not do
what it says.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 21:13:40 -07:00
Edward Langley 5d1bf75271 Merge remote-tracking branch 'gh/main' into fix/dsxq-yank-value-proj
# Conflicts:
#	context/test-evidence/clients.md
2026-09-22 21:11:31 -07:00
Ed Langley a120540e32 Merge pull request #169 from fiddlerwoaroof/fix/ktlk-editor-one-home
CI / Build all bundles (push) Has been cancelled
CI / Test and lint (host) (push) Has been cancelled
CI / wasm32 check (browser bundle crates) (push) Has been cancelled
CI / Browser JS unit tests (node --test) (push) Has been cancelled
CI / Knowledge base consistency (push) Has been cancelled
CI / Lint the harness crate (push) Has been cancelled
CI / Browser harness (OPFS contract + shell) (push) Has been cancelled
CI / Deploy to GitHub Pages (push) Has been cancelled
The editor overlay and Enter read one buffer (improvise-ktlk)
2026-09-22 21:08:56 -07:00
Edward Langley 99797918bc WIP: project the evaluated value; yank copies it
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 21:05:25 -07:00
Edward Langley 97d17b60f2 Merge remote-tracking branch 'gh/main' into fix/help-keys-checks 2026-09-22 21:05:21 -07:00
Edward Langley a06f50560d The forward-step test pins its key through resolve, not by name
composed() dispatches on the command name and ignores the event, so the
',' it was given pinned nothing: rebinding view-forward to '~' left the
test green (improvise-3ij0). resolve() looks the key up in the shared
keymap first, so the same rival now fails at that assertion.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 21:04:03 -07:00
Edward Langley bdf9bff472 The editor overlay and Enter read one buffer (improvise-ktlk)
PR #162 (improvise-xg1z) made render.rs paint edit_seed when Editing
arrived without its edit buffer, but commit and append-char still read
the absent buffer as the empty string. So the overlay showed the value,
Enter composed ClearCell, and a typed character replaced the text
instead of extending it: two derivations of what is in the editor.

The fold now seeds the missing buffer (ClientState::settle_editor, next
to settle_mode), and render.rs reads the buffer through read_buffer, as
commit does. edit_seed goes back to pub(crate) and no renderer calls it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 20:53:51 -07:00
Edward Langley 334e218c7b s8h7: i on an aggregate names drill, in help and in the browser
In the TUI, EditOrDrill hands an aggregated cell to DrillIntoCell, so `i`
on an aggregate is drill by another door. The help page called it only
"Edit current cell", and the browser refused it in words unrelated to `>`'s,
so a user could not tell the two were one missing feature.

- help.rs: the edit-or-drill row says it drills into an aggregate.
- help.rs: Normal-mode Enter was captioned "Edit current cell (same as i)"
  but is bound to enter-advance alone, which only moves down; the row moves
  to Cursor movement and says so.
- resolve.rs: the Aggregate arm now reads "an aggregate is edited by
  drilling in — " + not_yet("drill"), the same sentence `>` uses.
- context/code/clients.md and the evidence entry reverse the recorded
  "different sentences on purpose" rationale, which was false about the TUI.
- harness/tests/drill.spec.js follows the new wording.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-22 20:53:18 -07:00
Edward Langley 812f555958 Merge remote-tracking branch 'gh/main' into fix/hs0u-lut7-browser-prefix
# Conflicts:
#	context/test-evidence/clients.md
2026-09-22 20:19:38 -07:00