Skip to content

Feature: component loaders/placeholders — anti-flicker + developer-defined skeleton templates #255

Description

@silverbackdan

Summary

Reduce loader flicker / layout shift while CWA resources load, and let developers register a front-end placeholder/skeleton per component type that renders in place of the bare spinner until the resource resolves — ideally reserving the right space to avoid CLS.

Two phases:

  1. Anti-flicker (immediate): stop the current bare spinners popping in/out and shifting layout during normal loads.
  2. Developer-defined placeholders: opt-in per-component skeleton templates, laid out from the manifest tree so space is reserved before anything resolves.

Front-end only — no API/manifest change (api-components-bundle #198 closed as won't-do; rationale below).

Current state (flicker sources)

  • ResourceLoader.vuev-if="isLoading" renders a bare <Spinner> (isLoading = resource IN_PROGRESS with no data, or undefined + buffering). Pops in/out with no reserved space.
  • ComponentGroup.vuev-if="showLoader" renders a <Spinner> in a component-group-placeholder.

These appear/disappear as each resource flips state, causing spinner flicker and content jumping in.

Why this is viable with no API help

The nested manifest (resourceTree, NestedJsonStructure[], landed with #250) already gives us — up front, before fetchBatch resolves — the full tree of IRIs about to load plus their nesting + order. And each IRI yields its component type with zero extra calls:

  • coarse type ← IRI prefix (getResourceTypeFromIri)
  • specific component type ← /component/{collection}/{uuid} → reverse the {collection} segment via the resourceName → endpoint map getComponentMetadata already builds (e.g. /component/images/…Image)

So we can pick the right placeholder per node, and reserve layout from the tree, with zero API metadata.

Proposed shape (design in this issue)

  • Authoring convention: a placeholder alongside a component, e.g. app/cwa/components/<Name>/placeholder.vue (mirrors the existing admin/ + ui/ subdir scanning in module.tscwa-options.ts), auto-registered as the loading template for CwaComponent<Name>.
  • Rendering: ResourceLoader / CwaComponentGroup render the resolved placeholder (keyed by IRI-derived type) until the resource fetch resolves, then swap to the real component — placeholder ideally occupying the same box to avoid CLS.
  • Opt-in: no placeholder registered ⇒ current behaviour (or a generic default skeleton); developers choose per component.
  • Tree-driven layout: consider driving pre-content layout straight from resourceTree so ancestors/siblings can reserve space before any child resolves.
  • Anti-flicker baseline (phase 1): even without per-type placeholders, smooth the existing spinner (debounce/min-display, hold space, avoid flip-flop between undefined/IN_PROGRESS/buffering) so quick loads don't flash a spinner and slow ones don't jump.

Alternatives considered

  • Minimal per-item UI info from the API (e.g. which UI variant / a skeleton hint per node in the manifest): would let placeholders be more instance-accurate, but couples the manifest cache to component/UI edits — a change to a component would invalidate the manifest, undermining the piecemeal, independently-cacheable design and therefore overall responsiveness and speed (bigger, more-often-invalidated manifest responses). Deferred in favour of front-end-only. Revisit only if the front-end-only approach proves insufficient, and weigh the cache/speed cost then.
  • Per-node dimensions in the manifest — rejected (Bug: Page Reload Prompt appears when adding or modifying a page #198): embeds component internals + same cache-coupling problem. Skeleton shape/size defaults belong in front-end config per component type, not API data.

Acceptance criteria

  • Fast resource loads do not flash a spinner; slow loads show a stable placeholder that doesn't jump when the real content swaps in.
  • A developer can register a placeholder for a component type and see it render (in the correct slot, reserving space) while that component's resource loads, then swap to the real component.
  • No placeholder registered ⇒ graceful default (generic skeleton or current behaviour).
  • No API/manifest changes; layout/type derivation uses resourceTree + IRI-derived type only.
  • Covered by unit tests (placeholder resolution by type, swap-on-resolve, anti-flicker timing).

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions