Files
upbeatBytes/docs/news-migration-plan.md
T
thejayman77 099bf55711 docs: news relaunch migration plan (link/redirect map + interim routing)
Settled plan (user + Codex) for standing up /news as the feed's home and cutting
/home3 → / without breaking the feed, deep links, or SEO. Drives the upcoming
implementation; next build is the feed extraction (pure refactor). Includes the
four Codex amendments: /news noindex during transition, explicit prototype 301s,
explicit legacy-view mapping (shim before render + /news?view=today alias), and
the footer coverage inventory (FeedbackModal stays in the global layout).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:00:08 -04:00

6.4 KiB

News relaunch — link/redirect map + interim-routing plan

Scope: stand up /news (the feed's new home) and cut /home3/ (hub) without breaking the feed, deep links, or SEO. Verified against the codebase 2026-06-28. Settled with the user + Codex; amendments folded in. Next build = the extraction (§A, pure refactor).

A. Interim routing — no broken window, no duplicated impl

The feed currently IS routes/+page.svelte (~1,065 lines: views, BottomNav, MoodNav, LanePicker, SavedFlyout, search). Don't clone it. Extract once, mount twice.

  1. Extract the feed UI from routes/+page.sveltelib/components/NewsFeed.svelte (verbatim move; URL base becomes /news). parseView/urlForView switch base //news.
  2. Mount at both during transition:
    • routes/news/+page.svelte<NewsFeed/>
    • routes/+page.svelte<NewsFeed/> (interim; identical component) One implementation, both routes live, zero interim breakage.
    • /news stays hidden during transition: X-Robots-Tag: noindex, follow (so we don't publish a duplicate indexable feed). Removed at cutover, when /news is added to the sitemap.
  3. Parity test at /news (every view, deep link, Back/Forward, account action) — see §F.
  4. Restyle NewsFeed once (CD editorial + reusable card — replaces ArticleCard).
  5. Behavior fix (separate, deliberate, post-parity): Latest stays chronological; Highlights gets prefs + geo scope.
  6. Cutover: routes/+page.svelte → hub content (move from home3); add the legacy-query redirect shim (§C); drop /news noindex + add to sitemap; /news stays the feed.

B. Link/redirect map

Rule of thumb: brand/logo/"home"/"back to UB" → / (hub); anything "more news / browse / a feed view" → /news; feed-internal view URLs → /news?… base; /home3 fallbacks → /.

B1. Brand / home / back → / (KEEP)

  • Header.svelte:9 brand /; account:103 brand /, :108 back /; admin:678 brand /, :28 non-admin bounce /; auth/verify:24 post-signin / (DECIDED: always hub; future optional start-screen setting), :38 back /; zen:59 brand /; share.py:197,327 brand logo /, :354 "Back to Upbeat Bytes" /; api.py:781 unsubscribe back /.

B2. News CTAs → /news (CHANGE)

  • home3:198 "Read more good news" //news (the loop bug)
  • share.py:210 "Explore Upbeat Bytes →" → /news
  • share.py:332 "Browse more on Upbeat Bytes →" → /news

B3. Feed-internal view URLs → /news?… base (CHANGE, via the extraction)

  • routes/+page.svelte (→ NewsFeed): urlForView builds /?source=,/?tag=,/?view= (:39-41), search goto('/?q=') (:50), clear goto('/') (:689), urlForView('today') returns / (:344,503) → all rebase to /news.
  • account:196 Following /?view=following/news?view=following
  • share.py:30 source link /?source={id}/news?source={id}

B4. /home3 fallbacks → / (CHANGE)

  • HubBar:18 home nav /home3/; :28 brand /home3/
  • HubShell:17 back fallback goto('/home3')/
  • art:41, play:213 back fallback goto('/home3')/

C. Redirects

  • Legacy root-query shim on the new hub / — runs before the hub fetches/renders (no hub flash), replaceState:
    • /?view=today/news?view=highlights
    • /?view=latest/news (Latest is the default)
    • ?tag=, ?source=, ?q=, other ?view= → carry across to /news?…
    • /news?view=today remains an accepted alias (old links never break)
  • /home2, /home2.html, /home3, /home3.html/ permanent (301) (Caddy). A bare route delete would just serve the SPA fallback — redirect explicitly.

D. Infra

  • Caddy @hidden (currently /home2 /home3 /word /word.html /quote /quote.html /onthisday /onthisday.html /admin /admin.html):
    • REMOVE /word* /quote* /onthisday* (indexable at launch)
    • /home3*,/home2* → 301 redirects (out of @hidden)
    • /news carries X-Robots-Tag: noindex, follow until cutover, then removed
    • KEEP /admin*; /a/* still routed to FastAPI
  • Sitemap (api.py sitemap()): raise LIMIT 5000 → ~50000; gate on having a real summary (skip ~31 incomplete; ~+1,000 URLs); add static /news (at cutover) + /art /play /word /quote /onthisday (keep /,/today). Fix HEAD /sitemap.xml (currently 404).
  • Head patcher (patch-static-heads.mjs): add /news (title/desc/canonical/OG).
  • PWA description (manifest.webmanifest + app.html description/og/twitter): currently news-only — broaden to the hub (news + daily art + games + small resets).
  • Footer: ONE shared Footer.svelte — consistent core (motto + Send feedback) + a default slot for per-section extras. FeedbackModal stays in the global layout; only the layout's <footer.site> markup is removed (and HubShell's <footer.foot>). Coverage inventory — the shared footer must be explicitly added to every public surface: Hub, News, Play, Art, HubShell details, Account, Zen (if dev-visible). Admin/auth get a deliberate minimal treatment (explicit, not by omission).

E. Behavior (deliberate, post-parity)

  • Latest = newest accepted after safety/boundary exclusions; stop passing home (or add an explicit "Local first" lane) so Latest ≠ local-first.
  • Highlights = ranked around interests + geo scope dial (kept at launch).
  • trackVisit() stays global; markBriefSeen() only inside Highlights.

F. Cutover checklist (rehearsal first, hidden)

  1. GSC review (coverage / manual-actions / crawl) BEFORE rehearsal.
  2. Parity at /news: each view (today/latest/following/tag/source/search/mood/topic), deep links, Back/Forward single-history, account actions (save/follow/hide/replace), PWA.
  3. Legacy redirects: /?view=today/news?view=highlights, /?view=latest/news, /?tag=…,/?source=…,/?q=…/news?…; /home2*,/home3*/.
  4. SEO: / 200 + indexable + canonical; /a/* 200 + self-canonical (unchanged); /today 200 indexable; /news noindex dropped + in sitemap; joy/art/play noindex removed + in sitemap; sitemap GET + HEAD 200.
  5. Caches: anon Latest/Brief edge-cacheable (45s) intact; personalized private/no-store. SW is a kill-switch (no bump) — just verify.
  6. Promote → live 200/301/canonical/cache checks; resubmit sitemap in GSC.