Commit graph

76 commits

Author SHA1 Message Date
Your Name
1c6283059e style: drop-target and mark-editor styles
Glowing drop targets for annotation reparenting (cards + breadcrumbs), the
per-endpoint re-pick ✕, and the drag-to-reorder grip + drop line — the visuals
for the interactions landed in 7f3d903.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 10:11:17 -04:00
Your Name
0cf261c440 style: brighter, colored picker-mode glow
Replace the 1-bit black pulse on pickable clips/frame-readout/annotation bars
with a cyan↔magenta glow that swells and shifts hue, so what's clickable in
picker mode reads at a glance. Adds --pick-a/--pick-b for reuse.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 10:09:34 -04:00
Your Name
7f3d903452 feat: allow drag reordering marks, allow reparenting, allow removing endpoints of mark 2026-07-02 10:05:36 -04:00
Your Name
0c5456d6df perf: stop toolbar/share-menu re-rendering every frame in playback
Both derefed the raw ::playhead in their render bodies, so the whole toolbar
(zoom/row sliders) and the share menu (even while closed) reconciled 60fps
during playback. toolbar now subscribes coarse ::at-start?/::at-end? edges
(flip only at the timeline ends) and reads the exact playhead at click time;
share-menu reads the playhead at click time too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 20:25:14 -04:00
Your Name
05514f3522 perf: stop full annotation-list re-render on every bar crossing
commentary dereferenced active-annotation/active-annotations/active-note-set
in its body, so the whole card list rebuilt (and React reconciled every card)
on every playhead bar-crossing during playback and on context enter — the
annotation-only jank + the enter "double flash".

Push the per-item active state into granular per-id subs
(::annotation-active? / ::note-active?): each card and each bound-note chip
subscribes to its own state, so a crossing re-renders only the item(s) that
flipped. The active-card scroll moves into a nil-rendering component that owns
the active-annotation sub, so commentary no longer re-renders for it either.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 20:22:01 -04:00
Your Name
d22bff7654 optimization: perf improvements 2026-07-01 19:53:57 -04:00
Your Name
40ff2ecf52 fix: live-sync drawings/notes; cut per-frame sub cost; pen color+size
- backend: the scene PUT only broadcast to peers when annotation counts
  changed, so drawing/script-note edits saved but never synced live (only
  after a reload). Broadcast any gid that actually moved, regardless of type.
- perf: active-drawings/active-note-set recomputed fully on every playhead
  tick (active-drawings even re-resolved each annotation per mark). Split the
  bar computation into memoized ::drawing-bars/::note-bars (scene/ctx/segs
  only) and reduce per-frame work to cheap interval tests.
- perf: only subscribe drawing-layer to the boil clock when something is
  actually animating (no more 7fps idle re-renders).
- drawings: pen colour picker + stroke-size slider, still defaulting to the
  annotation colour per session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 19:29:32 -04:00
Your Name
9f06b51f76 feat: wiggly-paint drawings bound to marks
A drawing is a first-class :type :drawing entity (normalized strokes +
seed), bound to a mark via mark :drawings [gid] — same referrer-pointer
model as notes. It renders as a boiling SVG overlay on the video whenever
the playhead is in the bound mark's span (the same hook that lights an
annotation active), including when that annotation is the pushed context.

- Author in an overlay on the video monitor: pencil, eraser (deletes a
  whole stroke), undo; the 🖼 button on each mark row enters draw mode
  and seeks to the mark's frame.
- Boil is procedural: 3 seeded jitter variants of the base paths cycled
  at ~7fps; only the strokes persist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 18:03:44 -04:00
Your Name
b4924aa992 feat: script notes — named PDF-region annotations bound to marks
Replace the old per-annotation :script rects with a first-class
:script-note entity (name, color, id'd regions), persisted/synced
through the same generic scene channel as every other group.

- Author regions by selecting text on the pdf.js text layer; a region
  stores char offsets + per-line rects + per-range commentary.
- Bind notes to an annotation or a specific mark (drag-drop); a bound
  note shows »»» while the playhead is within its span.
- Script pane: note rail with per-note eye toggles, fit-to-width + zoom
  controls, plain-highlighter rendering (multiply blend, no borders).
- Click a highlight to open + focus its note; click a note chip on an
  annotation card to jump to its first passage.
- Schema v2 migration drops the legacy :script rects.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 16:34:29 -04:00
Your Name
89533a13cd fix: prevent scroll reset when hovering over hidden annotation
Keep button container always rendered but use visibility:hidden to hide
buttons instead of conditionally rendering them. This preserves DOM
structure and prevents scroll position reset on hover.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-30 16:51:35 -04:00
Your Name
06b28e2a9c refine: hidden annotations show edit + expand buttons on hover
Remove the unhide button. Instead show Edit (✎) and Expand (⤢) buttons
on hover so users can edit for any reason or expand to zoom in.

Make title text smaller (10px) and reduce card height to 24px.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-30 16:49:37 -04:00
Your Name
4ffd831551 feat: show hidden annotations as collapsed cards in annotation pane
Hidden annotations appear in the annotation pane as small, disabled-looking
cards with the dither pattern overlay (var(--desktop)) at 40% opacity.

On hover, an "Unhide" button appears to remove the hidden flag.
Full content, controls, and metadata are hidden.

Hidden annotations still work in marks/autocomplete and maintain their marks.
They appear as normal bars in the timeline.

Add 'Hide from timeline' checkbox in the annotation edit form.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-30 16:38:38 -04:00
Your Name
1ea675567c feat: add hidden annotation meta for decluttering timeline
Add 'hidden' meta flag to annotations to hide them from the timeline lane
while keeping them available for linking. Hidden annotations appear as
small 6px colored rectangles with faint color and dither pattern overlay.

Click on a hidden rectangle to unhide it. Hidden annotations' marks are
not resolved/used, and they don't trigger scroll-to-clip during playback.

Add 'Hide from timeline' checkbox in annotation create/edit form.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-30 16:27:19 -04:00
Your Name
e964347e51 fix: adjust timeline layout for time ruler height
Increase gutter-lanes height by 24px to align with the time ruler above the
annotation lanes. Update timeline-body-scroll padding to match.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-30 16:10:26 -04:00
Your Name
1cdb0de042 fix: scale time ruler tick marks to prevent overlap, use HH:MM:SS format
Dynamically calculate tick intervals to maintain ~80px minimum spacing between
marks regardless of zoom level. Prevents overlapping labels at high zoom.

Change time format from adaptive (m/s/·) to consistent HH:MM:SS or MM:SS format.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-30 16:07:05 -04:00
Your Name
d93b92f4a5 feat: add time ruler with tick marks above timeline
Display adaptive tick marks and time labels above the timeline to indicate
intervals. Tick spacing automatically adjusts based on zoom level:
- < 10 px/sec: 30s intervals
- < 20 px/sec: 10s intervals
- < 40 px/sec: 5s intervals
- < 80 px/sec: 1s intervals
- >= 80 px/sec: 0.5s intervals

Labels show minutes (m), seconds (s), or deciseconds (·) depending on interval.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-30 14:15:11 -04:00
Your Name
52c700b843 fix: scroll timeline to deep-linked playhead on first load
When a project opens with a ?f=<frame> query parameter, the playhead position
is now visible in the timeline view. Previously, the playhead was positioned
correctly in the UI but the timeline didn't scroll to show it. This triggered
scroll-to-seg-track on first render when playhead > 0 and segments are loaded.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-30 14:06:20 -04:00
Your Name
d91ea6c78a fix: dedupe scroll-to-clip by bar boundary instead of track
When an annotation with scroll-to enabled spans multiple bars (discontinuities),
only scroll when the playhead enters a new bar, not for every mark. Track
[ann-id bar-idx] pairs instead of just the current track to avoid excessive
scrolling as the playhead moves within a bar.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-30 14:05:58 -04:00
Your Name
6a02b0665b feat: clear project db on nav + clean editor loading/error states
Stale/fail flashes came from the editor rendering the previous project's
:project/:scene/:view (and a stale :load error) while the next loaded. Reset
those keys when opening a project or leaving to the list/create page, and render
the editor body only when :ready — otherwise a centered spinner ("Loading…") or
error message, with the top bar (and back button) still available.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:29:47 -04:00
Your Name
d9059ad132 fix: start video at deep-linked time via #t= media fragment (iOS)
The real iOS issue: it won't load the media until the first tap and ignores JS
currentTime until the element is seekable (populated very late), so every
load/seek hack started playback from 0. A #t=<seconds> media fragment on the
src is honoured by the media loader during the tap-triggered load itself, so it
starts at the right frame (and renders that frame instead of black). Bake the
deep-linked playhead into the src fragment, cached per clip so live playhead
changes don't reload the element; currentTime still drives in-session seeks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:21:33 -04:00
Your Name
a62af2584b Revert "fix: play from the playhead, not currentTime (iOS deep-link seek)"
This reverts commit 5c58af9036.
2026-06-30 11:17:42 -04:00
Your Name
484351746f Revert "fix: defer video seek until seekable, flush on loadedmetadata (iOS)"
This reverts commit 7585dd4e7f.
2026-06-30 11:17:42 -04:00
Your Name
7585dd4e7f fix: defer video seek until seekable, flush on loadedmetadata (iOS)
The previous fix still failed on iOS: at the first tap the element isn't
seekable (readyState 0), so the seek in engage-play! was dropped, and the
existing on-loadedmetadata seek is guarded by (when-not @play) — already true by
then — so it was skipped too, playing from 0. Now seek-video! stashes the target
when the element isn't seekable yet, and loadedmetadata/canplay flush it
(regardless of play state).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:02:54 -04:00
Your Name
5c58af9036 fix: play from the playhead, not currentTime (iOS deep-link seek)
On iOS the element doesn't load (so a deep-linked pre-seek doesn't stick) until
the user's first tap, so engage-play! read currentTime=0 and played from the
top. Seek to the playhead inside the play gesture and drive from there; for
normal pause/resume the playhead already equals currentTime, so it's a no-op.
Drops now-unused seg-at-src.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:54:53 -04:00
Your Name
28aa2622f6 ui: unhighlight between marks; translucent clip label
- ::active-annotations no longer falls back to the nearest annotation, so when
  the playhead is outside every mark nothing is highlighted.
- clip label background is translucent (color-mix paper 70%) like the lane
  bars, so the thumbnail reads through while text stays legible.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:47:33 -04:00
Your Name
d877655b7a ui: bolder/colored active annotation, readable clip labels, multi-active
- active annotation is now loud: thick colour spine, a wash of its own colour,
  a hard offset shadow, bold title (was just a faint 2px shadow). Annotation
  colour drives it via a --ann-color custom property; added a colour swatch by
  each title so the commentary reads its colours.
- clip name is a solid bordered label chip pinned top-left instead of black
  text + white halo, which muddied over busy thumbnails.
- highlight ALL annotations whose marks contain the playhead, not just one
  (new ::active-annotations set sub), so overlapping marks light up together.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:45:34 -04:00
Your Name
7e32dc70dd feat: PWA restores last project + playhead/stack on reopen
A homescreen launch opens the manifest start_url ("/") with no route, so the
deep state was lost. Persist the in-app URL (already kept current with stack +
playhead) to localStorage, and on a standalone cold launch with a bare hash,
re-enter it before the router reads the URL. Browser tabs are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:45:34 -04:00
Your Name
b031a167a4 fix: clip-follow only when playhead is inside a scroll-to annotation's bar
::active-annotation falls back to the nearest annotation (it drives the
commentary highlight), so gating playback clip-follow on it fired for every
clip. Gate on strict bar containment of a :scroll-to annotation instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:37:49 -04:00
Your Name
baa46cb734 feat: jumps always scroll to clip; scroll-to flag = clip-follow on playback
Reframe the per-annotation :scroll-to flag. Jumps/links now ALWAYS scroll the
target clip into view (unconditional, no flag). The flag instead drives normal
playback: while the playhead plays through an annotation that opts in, each of
its clips is scrolled into view as the playhead crosses onto that clip's track
(deduped to fire only on track change; reset when playback stops). Form label
updated to "Follow clips during playback".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:33:21 -04:00
Your Name
a99485ff48 refactor: scroll-to-clip via scrollIntoView on the gutter label
Replace the manual lane-h/row-h/clientHeight math (fragile, clamped oddly for
top rows) with scrollIntoView on the target track's gutter label. The label
lives in the fixed track-name column, so the browser scrolls the vertical axis
only — clean, no geometry, and structurally can't fight the horizontal follow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:28:00 -04:00
Your Name
602c710377 feat: per-annotation scroll-to flag gating vertical scroll on jump
Add :meta {:scroll-to bool} to annotations, toggled by a "Scroll to clips on
jump" checkbox in the annotation form. goto! now separates follow? (horizontal
recenter) from scroll-to? (vertical scroll to the clip's track); the vertical
scroll only fires for annotations that opt in, resolved from the canonical
scene group at each jump site (jump button/popover, script highlight, link
chips). Persists for free via editable-group + the backend's opaque group
merge; round-trips through json keywordize, so no migration needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:19:01 -04:00
Your Name
0c305e42ea feat: smooth horizontal + vertical scroll on jump/link
follow! gains an optional smooth flag. The jump/link path (goto! with follow?)
now glides both axes; playback-follow stays instant (smooth would fight the
per-frame updates) and scrubbing doesn't scroll at all, so clicking stays snappy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:10:55 -04:00
Your Name
967ca97e84 feat: scroll vertically to the clip's track when jumping via link/jump
goto! with follow? already recentred horizontally on the playhead. Now it also
scrolls the tracks container vertically so the clip under the landing frame is
brought into view — for link chips, annotation jump buttons, and script
highlights. No-op when the tracks already fit. Accounts for the pinned
annotation-lane header height in the scroll math.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:04:25 -04:00
Your Name
96da4a6bd8 feat: HyperCard-style top title bar (logo, project name, share, user menu)
Replace the mid-page .project-bar with a proper top nav bar pinned above the
video/timeline, styled like a classic Mac window title bar (centered title
flanked by racing stripes).

- left: TL wordmark + thumbnail-status indicator
- center: ‹ back-to-projects + project name, between the title stripes
- right: Share popover and a user dropdown with Log out
- Share offers two clearly named links, copied to clipboard:
  "Copy link to this moment" (current context + playhead) and
  "Copy link to the whole project" (root)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:58:54 -04:00
Your Name
28aafc047f fix: thumbnail parser overflow, autostop-killed jobs, unified playhead
Thumbnails failed immediately on any clip past ~2 min. The one-pass select
expression joined frames with a flat a+b+c+... chain, which overflows ffmpeg's
recursive expression parser past ~120 terms ("Error while parsing expression").
Join the same terms as a balanced tree (depth O(log N)) instead — identical
selection, still one decode pass, exact per-tile frames preserved.

- generate_thumbnails management command: runs synchronously and streams each
  phase to stdout, so jobs are watchable without tailing app logs
- surface ffmpeg's stderr instead of swallowing it in CalledProcessError
- fly: min_machines_running = 1. Background thumbnail jobs outlive the HTTP
  request that triggers them, so scale-to-zero let the proxy autostop the VM
  mid-job and leave projects stuck "running"
- timeline: replace the two split playhead bars (pinned lanes vs scrolling
  tracks) with one full-height overlay, positioned in JS to track horizontal
  scroll and sit above the translucent header

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:48:08 -04:00
Your Name
20ae2b4316 actually scroll to playhead 2026-06-30 02:52:38 -04:00
Your Name
96639554e4 translucent stuff etc 2026-06-30 02:42:31 -04:00
Your Name
5bc53c03f3 fix: generate thumbnails in one ffmpeg pass, not ~N/30
ffmpeg's select filter re-decodes the whole clip on every pass, so chunking by 30
turned a 670-frame job into 23 full decodes (minutes on shared CPU — long enough
to be killed mid-run and leave thumbnail_status stuck at 'running'). One pass now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 01:36:27 -04:00
Your Name
b05b440922 feat: upload a script PDF from the script pane
Backend: POST /api/projects/<id>/script/ (owner/collaborator) saves the script
FileField and returns the refreshed detail. Frontend: when a project has no
script, the script pane shows a PDF file picker that uploads on selection; the
returned detail carries the new URL so the pane renders it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 01:09:16 -04:00
Your Name
9c4b56dbc6 feat: per-annotation schema version + migration framework
Each stored annotation carries :v (schema-version, currently 1). tl.scene gains
a transformer registry and migrate fn that chains version upgrades on load;
save-group and open-draft stamp the current version. Empty transformer set at
v1 — bump schema-version and add a transformer when the model changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 01:06:14 -04:00
Your Name
7e47ec6a2d feat: upload progress on new-project form
Swap the create-project upload from :http-xhrio to a raw XHR effect with
upload.onprogress, surface it as a 1-bit progress bar; disable Create while
uploading.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 01:03:42 -04:00
Your Name
533d14ce49 ui: monochrome unicode glyphs + zoom arrow icons
- Zoom X/Y sliders labelled ↔ / ↕
- dark toggle ◐/◑, pause ❚❚, warnings △, drop 🔗 — no color emoji in the
  1-bit theme

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 01:01:27 -04:00
Your Name
ed5db0ce17 rename app UI to tl (Fly app/domain stay lumet)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 00:56:22 -04:00
Your Name
e6d150c375 feat: production deploy (Fly + R2) + frame controls and picker glow
Deploy:
- Multi-stage Dockerfile (shadow-cljs release → Python+ffmpeg runtime), fly.toml
  (one machine, scale-to-zero, SQLite on a volume), .dockerignore, DEPLOY.md
- Same-origin release build: api-port defaults "9001" for dev, "" in release
  (no CORS in prod)
- env-driven settings (SECRET_KEY/DEBUG/ALLOWED_HOSTS/DB_PATH/CSRF), WhiteNoise
  serving static + the SPA, R2 (S3-compatible) media storage when R2_* is set
- thumbnails reworked storage-agnostic: download clip, ffmpeg select-pass, upload
  tiles via default_storage (R2 in prod, disk in dev)
- pin channels/daphne; add whitenoise/django-storages/boto3

UI:
- frame step buttons flanking play (disabled at timeline bounds)
- Add-annotation button moved into the toolbar, made prominent
- picker mode pulses a 1-bit glow on clips / frame readout / (while linking)
  annotation bars; linking an annotation bar inserts a timeline link

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 00:56:09 -04:00
Your Name
e00a056209 feat: HyperCard theme + dark mode
- Retheme the whole app 1-bit HyperCard: white cards, hard 1px black borders,
  Chicago/Geneva/Monaco fonts (with web fallbacks; real fonts go in /fonts, see
  fonts/README.md), inverted-on-press buttons, a 50% desktop dither, black Mac
  title bar. Palette runs on CSS variables.
- Dark mode toggle (toolbar + home header): flips the palette vars via a class
  on <html>, persisted in localStorage and applied before first paint.
- Per-annotation fat colored left stripe in the annotation pane; annotation name
  bumped above the md h1; full-color (no longer veiled/desaturated) thumbnails.
- Jump popover now labels each part with the clip name + crop-adjusted mark-time
  frame (e.g. "CU Tashi (1) 45f"), matching the annotation editor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:23:34 -04:00
Your Name
fc25dfc016 feat: timeline links, network error handling, Tailscale access, mobile fixes
Backend / dev:
- Bind dev server to 0.0.0.0 and allow Tailscale MagicDNS + tailnet-IP origins
  in ALLOWED_HOSTS / CORS / CSRF so the app is reachable over Tailscale
- Unified, idempotent dev launcher (Django + media + shadow watch), hot reload,
  no manual compiles

Frontend:
- Migrate fetches to re-frame :http-xhrio with failure handling; surface
  network / load / save errors where the user expects them
- Extract markdown + link formatting into tl.md: parse/serialize round-trip
  (recursive chip serialization fix), block markdown (headings/lists/paragraphs)
- Timeline-opening links: navigate the stack to any reachable timeline;
  autocomplete lists all timelines (root included, orphans dropped) with a live
  preview that pushes the stack and reverts on commit/cancel
- Annotation pane shows the current context's description with edit-in-context
  (drops into the parent timeline for marks, pops back when done) + root content
- Fixes: deep-link playhead seek, replay-from-end, mobile input zoom, notch
  safe-area insets, autofocus the content field, freeze form to snapshot context

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 19:24:40 -04:00
Your Name
3d3b031332 feat: add route state and merge patches 2026-06-29 14:24:49 -04:00
Your Name
5b206635fb feat: reuse marks picker for annotation links 2026-06-29 12:14:08 -04:00
Your Name
d3c8dcbdf2 Script pane, realtime peer sync, and logout button
- Script pane: PDF tab beside annotations; "Add script annotation" arms
  highlight mode on the draft, ✓/✕ to confirm/cancel, auto-scrolls to the
  active annotation's highlights during playback. Adds Project.script.
- Realtime: Django Channels websocket per project broadcasts attributed
  scene deltas after each save; peers merge them in (keeping local drafts).
- Logout link in the editor project-bar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 10:52:46 -04:00
Your Name
595ac5ba72 json bug fix 2026-06-29 04:01:52 -04:00