#
Hostr App Audit (Visual + Logic)
Date: 2026-02-23
Scope: app/lib/presentation/**, key app cubits, and hostr_sdk/lib/** usecases/streams/payments/seeding.
This document is intentionally findings-only and plan-only (no code changes yet).
#
Visual
#
1) Spacing system audit
#
Findings
- Spacing primitives are inconsistent across the UI:
SizedBox(...)used heavily in presentation (many raw literals like4,6,8,10,12,15,16,24,40).CustomPaddingexists and already ties tokDefaultPadding, but is not consistently used.Spacer()is used very little relative to manual fixed-width/fixed-height spacing.
- Practical impact:
- Visual rhythm changes between screens/components.
- Harder to refactor density for compact/large form factors.
- Increased cognitive noise from micro-inconsistencies.
#
Principle to adopt
Use a single spacing scale derived from kDefaultPadding:
- Example token map (recommended):
spaceXs = kDefaultPadding / 8spaceSm = kDefaultPadding / 4spaceMd = kDefaultPadding / 2spaceLg = kDefaultPaddingspaceXl = kDefaultPadding * 1.5
- Introduce 2 reusable widgets:
AppGap.h(multiplier)/AppGap.w(multiplier)DefaultSpacer(multiplier)for flex layouts where proportional growth is desired.
#
Priority hotspots
- Listing/detail and profile widgets with many literal
SizedBoxvalues. - Payment flows and modal content where spacing currently mixes multiple scales.
#
2) Typography audit
#
Findings
- There are only a small number of explicit
fontSize:overrides, but they are not tokenized. - Most text is theme-driven (good), but explicit overrides remain in profile/payment/reservation/search marker areas.
#
Best principle for font sizes
- Keep a small typographic set to reduce cognitive load:
- Display (rare)
- Title
- Body
- Label/Caption
- In Flutter: prefer
ThemeData.textThemeeverywhere. - Avoid ad-hoc
TextStyle(fontSize: X)unless the style is first registered as an app token/semantic style.
#
Recommended target
- 90%+ of app text should use
textThemesemantic roles. - Explicit size overrides only for exceptional UI (e.g., map marker rendering internals).
#
3) Icons, icon buttons, bitmap pills
#
Findings
- Icon sizes are inconsistent (
12,14,16,18,20,30,48, etc.) and not tokenized. - Icon button usage is mixed (sometimes icon-only where textual affordance would be clearer).
- Price marker pills are custom-rendered and generally good, but text/icon sizing should align to typography tokens.
#
Principles
- Tokenize icon sizes: e.g.
iconSm,iconMd,iconLg,iconXl. - Use icon-only buttons only when intent is universally obvious (close, copy, back, overflow).
- Use icon+label for actions with consequence/ambiguity (fund, swap, escrow actions).
#
4) Button hierarchy
#
Findings
- Multiple button types are used (
FilledButton,ElevatedButton,OutlinedButton,TextButton,IconButton) with inconsistent semantics. - Some equivalent actions use different variants across screens.
#
Recommended hierarchy
- Primary:
FilledButton(one per area/modal). - Secondary:
OutlinedButtonorFilledButton.tonal. - Tertiary:
TextButton. - IconButton: utility actions only.
Define usage rules in a small design contract and enforce via component wrappers.
#
5) Animation consistency
#
Findings
- Global animation constants exist (
kAnimationDuration,kAnimationCurve,kStaggerDelay) which is excellent. - Several widgets still use literal durations (
200ms,400ms,1s,1.5s,2s, etc.).
#
Principle
- One shared motion system:
- Enter/exit duration
- Emphasis duration
- Debounce duration tokens
- Shared curves
- Any literal duration should be justified by domain behavior (polling/network timeout) not visual preference.
#
6) Modal style consistency
#
Findings
- You already have a reusable
ModalBottomSheetcomponent (strong foundation). - Many call-sites still directly invoke bottom sheets/dialogs with varying style density.
#
Principle
- Route all modal content through one modal shell component.
- Keep title/subtitle/body/button zones identical across flows.
#
7) Perceived performance (preload + placeholders)
#
Findings
- You already preload listing images (
ImagePreloader,PreloadListingImages) — this is good. - Some image states still use generic placeholders/progress indicators causing layout and perceived-jank differences.
- Filter screen and some bottom sheets are built only on click; this can feel delayed when dependency graph is heavy.
#
Good practice guidance
- Preload expensive screens only when:
- high-open-frequency,
- high-build-cost,
- no stale-data risk.
- Use skeleton/fixed-size placeholders to prevent resizing jumps.
- Keep dimensions stable during loading (especially media cards and carousels).
#
8) Translation/i18n
#
Findings
- Localization support is present and used in many places.
- There are still many hardcoded English strings in presentation and widget files.
#
Principle
- User-visible strings must go through localization layer.
- Reserve inline literals for temporary debug/dev surfaces only.
#
Visual execution plan
#
Phase V1 — Design tokens + wrappers
- Add spacing/icon/typography/motion tokens in one source of truth.
- Add wrapper components (
AppGap,AppButton,AppModal, icon size helpers). - Document usage rules (short markdown spec).
#
Phase V2 — Bulk refactor (safe mechanical)
- Replace raw
SizedBoxliterals with spacing tokens/wrappers. - Replace ad-hoc
fontSizewith textTheme semantics. - Normalize icon sizes and button variants.
#
Phase V3 — Motion + modal normalization
- Replace visual literal durations with motion tokens.
- Move bottom-sheet/dialog call sites to shared modal shell.
#
Phase V4 — UX polish/perf
- Add skeleton placeholders with fixed dimensions.
- Pre-warm high-frequency entry screens (filters/payment chooser) where warranted.
- Validate in Widgetbook + integration screenshots.
#
Visual acceptance criteria
- No raw visual spacing literals outside approved token helpers.
- No ad-hoc font sizes outside tokenized typography styles.
- All modal flows share same shell and button hierarchy.
- Consistent animation timing across presentation layer.
#
Logic
#
1) High-load hotspots across app + sdk
#
Findings
- Potential heavy fan-out from per-item subscriptions in listing-related widgets.
- Search map marker updates rebuild markers asynchronously and can churn under rapid list updates.
Requests.count()currently materializes full query results and counts in memory (can be expensive).- Some thread/message rebuild paths can become O(n^2)-ish for larger datasets.
#
Recommendation
- Move toward aggregate/list-level subscriptions instead of per-item network subscriptions.
- Use batched updates and incremental diffing for maps/lists.
- Prefer protocol/server count mechanisms where available; avoid
toList().lengthfor large streams.
#
2) Stream lifecycle + listener cleanup
#
Findings
- Many areas correctly cancel subscriptions on
dispose/close. - However lifecycle policy is mixed (manual lists in some modules,
takeUntil+ dispose subject in others). CrudUseCaseowns a broadcast_updatescontroller but has no explicit dispose contract.
#
Best practice recommendation
- Standardize to one pattern per layer:
- UI/cubits: maintain explicit subscription registry + deterministic
close/dispose. - Reactive pipelines:
takeUntil(_dispose$)for derived streams.
- UI/cubits: maintain explicit subscription registry + deterministic
- Every long-lived service with controllers/subjects should expose and be called with
dispose(). - Add leak tests for critical long-lived services.
#
3) Usecase correctness (CRUD/request batching/caching)
#
Findings
CrudUseCasebatching forgetOneandfindByTagis a strong pattern.- In-flight query dedup exists in
Requests(good). - Potential correctness/perf pitfalls remain in edge cases:
- fallback fan-out queries after unmatched batch requests,
- broad filters causing high relay load,
- non-uniform caching toggles.
#
Recommendation
- Introduce explicit query policy per usecase:
- cache strategy,
- timeout strategy,
- retry policy,
- dedupe key policy.
- Add metrics counters for batched requests, fallback rate, and duplicate subscriptions.
#
4) Error handling (EntityCubit, payments/swaps, parser)
#
Findings
- Error states exist but are mostly untyped/stringly in several paths.
- Payment/swap failures are surfaced but user messaging is not consistently actionable.
- Nostr parser currently throws on malformed event parse path; one bad event can poison a stream path if not isolated.
#
Recommendation
- Adopt typed domain failures (network/validation/protocol/auth/retryable/non-retryable).
- Add user-safe mapping layer from domain failures to UX copy + recovery action.
- Parser strategy:
- never crash broad stream on one malformed event,
- emit parse failure telemetry,
- quarantine invalid event and continue.
#
5) Nostr protocol future-proofing (immutability + migrations)
#
Findings
- Since Nostr events are signed/immutable, schema mistakes are costly.
#
Best-practice strategy
- Yes: add explicit protocol versioning in tags/content for your custom kinds.
- Prefer additive evolution (new fields optional) before breaking changes.
- On breaking changes:
- support dual read (
vN+vN+1) for a migration window, - write only latest version once app is upgraded,
- optionally publish newly signed migrated events when user key is available.
- support dual read (
- Keep parser backward compatible and tolerant to unknown fields.
#
6) Test architecture: fake relay vs real local stack
#
Recommended strategy
Use a layered test pyramid:
- Fast deterministic unit tests with fake request transport (no relay).
- Contract/integration tests with mock relay semantics (wipable state between tests).
- End-to-end smoke tests against real local relay + anvil.
Each tier should share scenario fixtures and setup/teardown primitives.
#
7) Seed builder extensions for repeatable scenarios
#
Findings
- Seed pipeline and
TestSeedHelperalready provide a good foundation.
#
Recommended extension set
- Scenario DSL for explicit actor states:
- user/listings/reservations/messages/payments/escrow statuses.
- Deterministic IDs and named fixtures per scenario.
- Snapshot seed packs for “open this exact page in exact state”.
- Shared setup/teardown API used by widget tests, integration tests, and screenshot runs.
#
8) Widgetbook/page targeting + screenshot automation
#
For specific page + specific seed data
- Yes, you can run app-level integration tests that:
- bootstrap deterministic seed scenario,
- deep-link directly to target route,
- wait for settled state,
- capture screenshot.
#
For app-store screenshot pipeline
- Add dedicated screenshot scenario suite with stable mock/network assets.
- Generate named outputs per device/locale/theme.
- Make CI artifact upload + review part of PR workflow.
#
Logic execution plan
#
Phase L1 — Observability + contracts
- Define typed failure model and error mapping matrix.
- Add instrumentation for subscription counts, batch hit rate, fallback rate, parse failures.
- Add lifecycle ownership map for long-lived streams/controllers.
#
Phase L2 — Reliability hardening
- Standardize stream disposal pattern per layer.
- Add malformed-event tolerance in parser pipeline.
- Add actionable error UX for Entity/Payment/Swap flows.
#
Phase L3 — Performance and protocol hardening
- Reduce per-item subscription fan-out where possible.
- Optimize query/count paths and map/list churn.
- Introduce explicit event versioning policy + migration playbook.
#
Phase L4 — Testing + screenshots
- Build reusable scenario fixtures on top of seed pipeline.
- Split test suites by fake/mock/real tiers.
- Add deterministic screenshot runner + CI artifacts.
#
Logic acceptance criteria
- No known long-lived subscription leaks in smoke lifecycle tests.
- Parse failures are isolated and observable, not app-fatal.
- Payment/swap/domain errors map to clear user recovery actions.
- Deterministic scenario seeds can reproduce target pages for tests/screenshots.
#
Suggested rollout order (combined)
- Token foundations (visual) + failure taxonomy (logic).
- Mechanical UI consistency refactor + parser/lifecycle hardening.
- Seed scenario DSL + screenshot CI.
- Protocol versioning rollout + migration tooling.
#
Notes
- This report intentionally avoids direct code edits.
- Next step can be an implementation PR plan with concrete file-by-file changes and staged commits.