Skip to content

web(timeline): thin the three timeline pages onto a shared TimelineGate; host-cover TimelineState transitions #671

Description

@mdorman

Boy-scout follow-up: #653/#669 converged the tag pages but touched SiteTagPage,
which still carries #[expect(clippy::too_many_lines)]. Per the #306 thin-component
direction, decompose the three timeline pages — and, following the #655 example
(the analogous TagInput thinning, #328), do it so the decomposition turns
currently-exempt reactive code into host-tested code, rather than merely
shuffling wasm sub-components.

The three pages

SiteTagPage, UserTagPage, and UserTimelinePage (web/src/posts/component.rs)
are now byte-for-byte near-identical wiring around the shared crate::timeline
bundle: build a keyed Resource, TimelineState::default() + a loaded gate,
adopt the PageSeed seed, a resolve Effect, a spawn_load_more load-more, a
read_error Memo, then chrome + the error/loading/TimelineRows gate. All three
carry #[expect(clippy::too_many_lines)].

What to do (mirroring #655)

  • Host-testable core. #329 already split the pure model
    (TimelineCursor/LoadStatus/fold logic → host-tested timeline/state.rs) from
    the reactive TimelineState signal-bundle (wasm-only timeline/component.rs,
    gated at mod.rs:14). The one uncovered behavior is TimelineState's
    transitions (adopt/resolve/fail, and the page's "apply the fetched result"
    dispatch) — exempt today only because the bundle is wasm-gated. Relocate the
    TimelineState signal-bundle into host-compiled state.rs
    and cover its
    transitions under an Owner (the web::reactive::Invalidator / forms::Field
    convention web(tags): co-locate TagInput into the tags vertical (#328) #655 used) — no #[client_only]. This is the "decomposing made it
    host-testable" win.
  • Shared reactive residue → one component + one helper. Extract a TimelineGate
    #[component] (the error → loading → <TimelineRows/> gate, taking
    state/loaded/read_error/on_mutate/on_load_more + tag_context/empty_text)
    and a wire_timeline_resolve(state, loaded, initial_page) -> Memo<Option<String>>
    helper (the resolve Effect + the failure Memo). Both in crate::timeline.
  • Sweep all three pages onto the extracted pieces; each collapses to its
    page-specific bits (params, keyed Resource, PageSeed adoption, load-more fetch,
    chrome). Drop all three #[expect(clippy::too_many_lines)].

Acceptance

  • All three timeline pages free of #[expect(clippy::too_many_lines)]; no
    #[client_only] added.
  • TimelineState host-compiled with Owner-based tests covering adopt/resolve/
    fail (+ the apply-result dispatch); coverage stays clean (the relocated code is
    now measured — the tests are what keep the gate green).
  • TimelineGate + wire_timeline_resolve shared by all three pages (and consider
    home/cockpit, which already inline the gate differently — evaluate, don't force).
  • cargo xtask validate green (incl. e2e); behavior identical — the tag/profile e2e
    (incl. posts: converge SiteTagPage/UserTagPage onto the shared TimelineState bundle #653's user tag page smoke test) stays green.

Origin: #653/#669 (touched SiteTagPage). Model: #655/#328. Family: #301, #306.

Metadata

Metadata

Assignees

No one assigned

    Labels

    webWeb/frontend layer: Leptos CSR, server functions

    Type

    Projects

    Status
    Todo

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions