UI capability map

Start here when the question is about a product shape rather than a Go
package: "Can I build a live dashboard?", "Where should optimistic state
live?", or "Does this need a client framework?" The component catalog answers
what symbols exist. This guide maps jobs to primitives, state ownership,
delivery and scaling semantics, runnable proof, and the detailed API docs.

The state boundary

Use this as the default architecture:

6 lines
database/server = durable business truthsignals/store    = immediate UI projectionRPC              = mutation and reconciliationpolling          = passive freshness without a held connectionSSE              = push delivery when the connection is part of the truthHTML/data result = authoritative response

That boundary is more important than the individual component choice. A local
signal can make a control feel immediate, but it does not become business
truth. An optimistic projection can move first, but the RPC response commits
or rolls it back. SSE tells connected views that server state changed; it does
not confirm the user's own mutation.

What kind of UI are you building?

Every row distinguishes a capability from a packaged convenience. "Proof"
names a route in the runnable examples/site gallery (start it with
go run ./examples/site) or a complete example program. "Primary docs" points
to the public contract rather than repeating it here.

Job to be doneComposeState, delivery, and scaling semanticsRunnable proofPrimary docs
CRUD/admin or form-heavy screensui.DataTable, ui.Form, ui.FormField, entity CRUD, battery/adminThe database is truth. Filters, validation, pagination, and writes run on the server. RPC or normal form responses return authoritative HTML/data. CRUD requests are stateless and replica-safe; sessions and live invalidation still need the shared backends described in scaling./components/datatable, /components/form, and examples/backofficeForms, Admin, Interactive patterns
Optimistic mutationsui.OptimisticAction, ui.ToggleAction, versioned sortablelistA signal/DOM change is a temporary projection. The RPC owns validation and commit. Non-2xx rolls back; a versioned 409 refetches authoritative server HTML. Do not wait for SSE to confirm the initiating action./components/optimisticaction, /components/toggleactionOptimistic UI, Interactive patterns, Runtime contract
Live dashboards and streamed statusSSR charts/status components, store.Slice, island signals, data-fui-poll, SSESSR supplies the first complete view. The server owns metrics; signals project the latest value. Polling (data-fui-poll) is the recommended tier for passive metric refresh — any replica answers, no fanout, no held connection. SSE is best-effort push delivery for sub-second updates and may drop old frames for slow consumers; it needs WithFanout across replicas. Use an outbox/queue for lossless work./examples/live-dashboard, /components/rpc-signal, /components/linechart, /components/recordsummaryLive dashboards, Reactivity model, Signal store, Events and SSE, Scaling
Master/detail workspacesui.PaneHost, server-rendered detail fragments, routes for durable/deep-linkable identityThe route identifies the selected durable record. Opening/swapping a pane is in-page projection; RPC can return its detail HTML. PaneHost owns pane behavior, not data fetching. Reconstructable handlers are stateless; process-held pane/widget objects require affinity./examples/workspace, /components/panehostPane host, UI composition recipes
Sortable lists and Kanbancore-ui/patterns/sortablelist, stable keys, optional group/container/versionThe browser previews a reorder. RPC persists it. Non-2xx restores the prior DOM; versioned 409 responses refetch server-rendered column state for reconciliation. Durable order belongs in the database./components/sortablelistInteractive patterns, Runtime contract
Notifications, progress, and activity feedsui.NotificationBell, toast presets, progress, ui.Timeline, signals/SSEA toast is ephemeral projection; durable notification/read state belongs in the database. Progress can be an RPC result for user work or an SSE update for background work. An activity feed that must not lose entries comes from durable rows/outbox, not the lossy SSE buffer./components/notificationbell, /components/progress, /components/timelineWidgets, Events and SSE, Notifications
Server-authoritative reactive SaaSSSR screen + typed store + RPC signal/fragment swaps + SSE invalidationThe store is a typed client projection seeded from SSR. User mutations use RPC and receive authoritative HTML/data. Background/other-user changes arrive through SSE and trigger a signal update or refetch. Shared fanout makes the push lane replica-aware; durable side effects stay on outbox/queue consumers./components/signal-store, /components/rpc-form-signalSignal store, Interactive patterns, Events and SSE
Presence and collaborative awarenessisland.Manager presence topics + ui.AvatarGroupIdentity is derived by the server. Rosters are live, lossy, self-healing state, not an audit record. With fanout they merge across replicas; without it they are local to one process./examples/presence?presence=presence-demo, /components/avatargroupPresence, Scaling
Static/exportable UISSR screens + App.ExportStatic; client-only signals/theme/copy where usefulThe export is build-time truth: HTML and assets run without a Go server. Server RPC, SSE, and server-backed widgets are deliberately disabled and must not be presented as live.examples/static-siteStatic export, Runtime contract
SPA integration by deliberate choiceGoFastr HTTP/OpenAPI/MCP backend + Vue, React, Svelte, or another clientThe client framework owns browser state and rendering. GoFastr still owns durable data, authorization, validation, and API responses. This is a separate architecture, not a way to mix a second renderer into a GoFastr-managed screen.examples/spaEntity declarations, Security, SDKs

Decide where state lives

DecisionPrefer the first option whenPrefer the second option when
Local signal vs typed storeThe value has one small producer/consumer scope and can reset with the page.Many consumers share it, it needs SSR seeding, or app-global lifetime must be explicit. Use core-ui/store.
Computed signal vs server recomputationThe derivation is cheap, synchronous, presentation-only, and can be expressed by a CSP-safe registered reducer.The result depends on authorization, durable data, pagination/filter rules, expensive work, or business invariants. Return it from the server.
RPC signal vs fragment swapThe authoritative response is one text/value/attribute shared by bound consumers.The authoritative response is structured UI. Let Go render HTML and replace the island region.
Optimistic update vs pending-only actionThe action is reversible, the prior projection is known, and failure/409 reconciliation is designed.The effect is destructive, expensive, security-sensitive, or cannot be undone honestly. Show pending state, then commit from the response.
SSE latest-state/drop vs durable queue/outboxA future update heals a missed one: badge counts, status, invalidation, presence, dashboard refresh.Every event/side effect matters: billing, email, workflow transitions, audit, or ordered processing. Use battery/queue or the transactional outbox.
Process-local island vs reconstructable stateYou deploy one process or deliberately configure affinity, and losing ephemeral state on restart is acceptable.You need stateless load balancing or restart survival. Reconstruct from URL/session/database on every RPC and use fanout only for push delivery.
GoFastr UI vs Vue/React/SvelteSSR-first screens, server-owned rules, small generic JS, and HTML fragment reconciliation fit the product.The product fundamentally needs a client-owned render graph or an ecosystem-specific client library. Keep GoFastr as the API/backend boundary.

Stateless and affinity-bound islands

These terms describe server ownership, not visual appearance.

  • A reconstructable island reads route/query/session/database state for each
    request and renders the next authoritative fragment. Any replica can handle
    its RPC. This is the preferred shape.
  • An affinity-bound island holds mutable widget/island objects in one
    process. WithFanout can deliver an update to the replica holding an SSE
    connection, but it cannot recreate that object on another replica.
    Redesign the handler around reconstructable state — read from
    URL/session/database on every request — so any replica can serve any
    RPC. Sticky routing is not part of the contract; a portable session
    token (see Reactivity model) makes it unnecessary.
  • A client-only signal is neither durable nor affinity-bound server state. It
    is a projection and may reset unless declared app-global and reseeded.

Read Horizontal scaling before adding the second replica.

Delivery guarantees

LaneGuaranteeUse it forDo not use it for
RPC responseOne request receives one status and authoritative HTML/data responseMutations, validation, reconciliation, fragment swapsBroadcast to other sessions
PollingThe browser re-fetches a region on a Go-duration interval; any replica answers from the DBPassive freshness — counters, statuses, dashboards — without a held connectionSurfaces that need push under a second, or that must reflect the connection itself (presence)
SSE/event busBest effort; default slow-client mode drops the oldest queued framesLatest status, invalidation, presence, live dashboard updatesBilling, audit, exactly-once workflows
FanoutBest-effort broadcast between replicas; handlers run on every replicaMaking SSE/island push visible wherever the browser connectedSingle-execution side effects
Queue/outboxDurable, at-least-once processing; consumers must be idempotentWorkflows, email, webhooks, audit-adjacent side effectsImmediate browser confirmation

Performance claims are bounded by what is measured. Benchmarks
includes island RPC, SSE delivery/drop metrics, and full UI-host SSR. It does
not measure browser hydration or promise that lossy delivery became durable.

Capability, convenience, and non-goals

  • Capability means the public primitives compose the architecture and a
    runnable example proves the path.
  • Packaged convenience means GoFastr provides the product-shaped component
    or battery directly, such as DataTable, PaneHost, OptimisticAction, or
    battery/admin. A capability may exist without a one-call convenience.
  • Proven performance means a named benchmark exercises that exact lane.
    Treat unmeasured browser/network behavior as unmeasured.
  • Explicit non-goals: GoFastr does not specialize in canvas/media editors,
    timeline/video authoring, offline-first CRDT workspaces, or a client-owned
    virtual DOM. Use a purpose-built client library and integrate it through the
    API/plugin boundary when those are the center of the product.

Search vocabulary

gofastr docs --grep is substring search over the same embedded corpus exposed
by the framework docs MCP tools. Useful ecosystem terms for this guide include:

13 lines
reactive stateclient stateoptimistic UIrollbackreconciliationlive updatesrealtimeevent streamlive dashboardmutationderived statecache invalidationserver-driven UI

Examples:

4 lines
gofastr docs ui-capability-mapgofastr docs --grep "live dashboard"gofastr docs --grep reconciliationgofastr docs --grep "reactive state"

See also

Common mistakes

  • Starting from a component name. State the job, truth owner, mutation
    path, and delivery guarantee first; then choose components.
  • Calling a signal durable state. It is a UI projection. Persist business
    truth on the server.
  • Using SSE as mutation acknowledgment. The initiating RPC response is the
    acknowledgment and reconciliation channel.
  • Assuming fanout makes process-held widget state stateless. It only bridges
    the push lane; reconstruct state or configure affinity.
  • Promising durable realtime from the default event stream. Default SSE is
    latest-state delivery and can drop frames. Use queue/outbox for durable work.
  • Choosing a client framework by reflex. Use one when the product needs a
    client-owned renderer, not to recreate an island or fragment swap already
    covered by the runtime.