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>
This commit is contained in:
Your Name 2026-06-29 19:24:40 -04:00
parent 3d3b031332
commit fc25dfc016
12 changed files with 548 additions and 224 deletions

View file

@ -25,7 +25,8 @@ SECRET_KEY = 'django-insecure-it#u2wp)_qjx@cq61o*thw#*4_1*hk!%%lz#d+fww6^*l3j+zj
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'testserver']
# '.ts.net' matches any Tailscale MagicDNS name; the literal is the tailnet IP.
ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'testserver', '.ts.net', '100.82.84.50']
# Application definition
@ -134,8 +135,18 @@ MEDIA_ROOT = BASE_DIR / 'media'
DATA_UPLOAD_MAX_MEMORY_SIZE = 100 * 1024 * 1024
# The shadow-cljs dev frontend talks to this API cross-origin, with the session
# cookie, so allow its origin + credentials.
# cookie, so allow its origin + credentials. Over Tailscale the frontend may be
# reached either by MagicDNS name (*.ts.net) or by the raw tailnet IP
# (100.64.0.0/10), both on :8280 — match both forms.
CORS_ALLOWED_ORIGINS = ['http://localhost:8280', 'http://127.0.0.1:8280']
CORS_ALLOWED_ORIGIN_REGEXES = [r'^http://[\w.-]+\.ts\.net:8280$',
r'^http://100\.\d{1,3}\.\d{1,3}\.\d{1,3}:8280$']
CORS_ALLOW_CREDENTIALS = True
# Cross-origin POSTs carry the session cookie, so Django's CSRF check needs the
# frontend origins trusted (Origin header must match for unsafe methods).
CSRF_TRUSTED_ORIGINS = ['http://localhost:8280', 'http://127.0.0.1:8280',
'https://*.ts.net', 'http://*.ts.net',
'http://100.*']
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'