Current system → completion system

Ranking is only half the job.

Opal Home uses a deterministic, on-device ranker with a tiny live suggestion set, a soft anti-repeat bonus, and routes that mostly open a destination. It can surface the right idea—then stop before the user makes the promised change.

Pure local policy 3 live suggestion templates ~2-day recency bonus Shallow rule deep-link
Monday · 08:14 · morning bucket on-device
unblocks.week = 2 activeRule.strict = false install.days = 28 lastShown ≥ 2d

Eligible suggestion candidates

focus.no_unblocks live
51 + 3 + 1
rest.digital_detox live
21 + 0 + 1
sleep.trouble live
outside bedtime
Correct idea, incomplete outcome “Modify Rule” opens a rule sheet. The no-unblocks promise is not completed or credited.

01 · Current stack

One scroll. Three ranking zones.

The visible screen is explicit SwiftUI composition. Gemstones and score are fixed shell elements; the feed ranker fills top and supporting lanes, with an optional inline module inserted between them.

Gemstones · visually pinned
82
Today's OpalFocus score & breakdown
+7
Top lane · max 2
Workday Focus is activeRules status
01
No Unblocks. No excuses.Suggestion
02
Inline · optional
Top TodayAppears at ≥30m screen time
free
Supporting lane · max 3
12 day focus streakProgress
01
Your Focus ReportWeekly summary
02
Yesterday in real lifeInsight
03
01

Gemstones → Opal score

Gemstones have their own progress ordering. The score follows as shell content; neither competes in card ranking.

02

Top lane → supporting lane

Top admits at most two; supporting admits at most three and does not borrow unused top capacity. Ranked total is five.

03

One suggestion across both lanes

The suggestion family cap is one. It prevents simultaneous suggestion clutter, but it does not create rotation over time.

Visible maximum: 6 modules

Top Today is passive, cap-exempt, non-dismissible, and excluded from lifecycle accounting. It can make the 2 + 3 ranked stack visibly total six.

02 · Current algorithm

Signals in. Visible cards out.

The ranker is stateless and side-effect-free. App glue builds a snapshot; policy orders and admits candidates; routing removes unwired winners; lifecycle updates only after cards become visible.

01

Signals

User, setup, progress, usage, rule, sleep, immediate context, and local lifecycle.

02

Score + eligibility

Unknown values suppress dependent cards. Eligible suggestions receive local points.

03

Top order

Urgent rule context, recovery, setup, then fresh and themed suggestions.

04

Supporting order

Demoted rules, streak or insight, Focus Report, remaining suggestions, fallback.

05

Caps

2 top, 3 supporting, 5 ranked total, 1 suggestion family card.

06

Route filter

Cards without a wired destination are removed before render and display credit.

07

Lifecycle

Record last shown, hides, milestones, insight kind, education, and process identity.

Suggestion score

Eligible base+1
Fresh behavioral signal+3
Profile match · mechanism unused today+2
Never shown or last shown ≥ ~2 days+1
Recently hidden−2

Active hides are filtered before scoring. The ~2-day rule is a bonus, not a hard cooldown: a recently seen card can still win when the eligible pool is small.

Morning05–11
Day11–18
Evening18–22
Night22–05

Suggestion catalog reality

sleep.trouble

Bedtime-window suggestion; alternates Sleep Stories and Meditations.

Live provider
focus.no_unblocks

Fresh at 2+ weekly unblocks when an active session rule allows unblocks.

Live provider
rest.digital_detox

Available after seven known install days; routes to the Timer tab.

Live provider
focus.allow_list

Route exists, but the live provider does not populate its required rule signal.

Signal-dark
focus.open_budget

Route exists, but top-app opens are not wired into the current snapshot.

Signal-dark
sleep.improve

Cataloged, but explicitly deferred by ranking policy.

Deferred
sleep.no_phone_morning

Cataloged, but explicitly deferred by ranking policy.

Deferred
focus.time_limit_social

Waiting for a complete creation destination.

Deferred
focus.study_session

Cataloged, but explicitly deferred by ranking policy.

Deferred
focus.brain_music

Waiting on its soundscape destination.

Deferred

03 · Case study

“No unblocks” needs an ending.

Today’s card promises a stricter rule. Its CTA invokes the generic action hook and resolves the first active unblock-allowed session rule, but only opens that rule’s editor. The user must infer the change—and Home cannot know whether it happened.

Today

Promise mismatch
8:14● ●
Home
Focus · Distractions

No Unblocks. No excuses.

Create bullet proof focus rules with no unblock allowed.

+ Modify Rule
8:14● ●
‹ Home

Workday Focus

Running now · 9:00–17:00

Blocked apps12 ›
ScheduleWeekdays ›
DifficultyNormal ›
UnblocksAllowed ›
The relevant setting is somewhere in the generic editor. There is no guided delta, confirmation, or success state.
! Tap handled, outcome unknown. The route re-finds a rule and presents it. Leaving the editor changes no suggestion lifecycle beyond its normal card-ID history.

Proposed

Completion flow
8:14● ●
Home
2 unblocks this week

Make Workday unbreakable.

Turn off unblocks for this rule. Review the exact change before applying it.

Review change →
8:14● ●
‹ Home

Strengthen your rule

Workday Focus · Applies to future blocks

Before ○ Unblocks allowed
After ● No unblocks allowed
You will not be able to bypass Workday Focus while it is active. Its schedule and blocked apps stay the same.
Confirm & apply
8:15● ●

Workday is now unbreakable.

No unblocks are allowed while this rule is active. Everything else stayed the same.

completed · rule_7 × unblockPolicy
Outcome credited after state transition. The CTA carries the exact rule and lever; success is recorded only after the persisted unblock policy becomes strict.
target

Exact device-local rule instance—not “first matching rule” at navigation time.

lever

Typed change: unblock policy from non-strict to strict.

precondition

Revalidate rule exists, is editable, and still matches the promised before-state.

completion

Credit only after persistence succeeds and the fresh rule snapshot confirms the delta.

04 · Why suggestions repeat

The bottleneck is the pool, not the sort.

Current ordering is deterministic and sensible for its inputs. Repetition emerges because the pool is small, identity is coarse, and a card tap is not connected to the state change that should retire that actionable instance.

01 / SMALL POOL

Only three suggestions are live from provider signals

Bedtime help, no-unblocks, and digital detox carry most of the production variety.

few eligible alternatives
02 / FAMILY CAP

One suggestion per feed is not rotation

The family cap prevents a stack of suggestions in one session; it does not spread exposure across days.

density control only
03 / IDENTITY

Lifecycle is persisted by card ID

Most suggestions have no durable identity for template × rule × lever, so one completion cannot retire one exact opportunity.

coarse history
04 / RECENCY

Two days earns a point; it is not a cooldown

Recently shown cards lose a bonus but remain eligible. A high fresh-signal score can still dominate.

soft anti-repeat
05 / THEMES

Time buckets keep asking the same small theme

Morning, day, evening, and night preferences help relevance, but they narrow an already shallow pool.

relevance without breadth
06 / WIRING

Missing signals and CTAs keep candidates dark

Allow-list and open-budget signals are unavailable live; five more suggestion IDs remain deferred.

catalog ≠ live set

05 · Prioritized improvements

Fix completion before adding volume.

More templates without better outcome semantics will create more doors into generic screens. Establish the action contract first, then expand levers, variety, and data.

P0Foundation

Make suggestion actions completable

Ship the no-unblocks commitment path end-to-end and make it the reference contract for every actionable suggestion.

  • Typed deep-link: template, exact rule ID, lever, before-state, destination
  • Instance identity: template × rule × lever × version
  • Credit completion only after the domain state actually changes
  • Keep impression, CTA tap, dismiss, failure, and completion distinct
P1Experience

Build useful variety around typed levers

Move from generic “modify” suggestions to small, reversible or clearly confirmed deltas on known rules.

  • Levers: unblock policy, allow list, schedule window, session length, open/time limits
  • Hard per-instance cooldown after impression; retire after completion
  • Template and theme diversity across a rolling multi-day window
  • Commitment UI: why now, exact before/after, consequence, confirm, success
P2Expansion

Wire the backlog and learn from outcomes

Once actions close the loop, activate the broader catalog and use local, privacy-safe outcomes to improve selection.

  • Populate allow-list absence, top-open count, profile, category use, and pickup-time signals
  • Wire deferred destinations before making those IDs renderable
  • Capture local reasoned dismissals: not now, wrong rule, irrelevant, already done
  • Rank for expected completion and novelty—not taps alone

Commitment UI anatomy

One screen, one meaningful delta.

The flow should be narrower than the full rule editor. It explains why the suggestion appeared, names the exact rule, shows the material consequence, and makes confirmation deliberate without turning a recommendation into a scavenger hunt.

01Why now

“You unblocked twice this week.”

02Exact target

“Workday Focus,” not an inferred rule.

03Before → after

Allowed becomes no unblocks allowed.

04Consequence

What becomes impossible, and when.

05Confirm

A specific “Confirm & apply” action.

06Persist

Domain write, then fresh-state verification.

07Success

Name the applied rule and unchanged parts.

08Lifecycle credit

Retire this exact rule-lever instance.

06 · Weekday morning

Before: a loop. After: progress.

Same local signal, same Monday morning. The difference is whether the system treats a suggestion as a destination link or as a state transition with an observable end.

Before Current card lifecycle
MON · 08:14 Signal fires

Workday allows unblocks; weekly count reaches two.

MON · 08:14 Card wins

focus.no_unblocks scores fresh and preferred.

MON · 08:15 Editor opens

User sees the running rule and leaves without finding the lever.

NEXT ELIGIBLE MORNING Opportunity remains

The same rule state still qualifies; card-ID history only softens rank.

After Instance completion lifecycle
MON · 08:14 Instance created

no_unblocks × Workday × unblockPolicy.

MON · 08:14 Delta reviewed

Unblocks allowed → no unblocks, with consequences visible.

MON · 08:15 Change verified

Rule persistence succeeds and the refreshed snapshot is strict.

TUE · 08:14 Progress, not repeat

That instance is retired; another eligible opportunity—or no suggestion—wins.

The product shift: optimize Home for completed, useful state changes while preserving the current ranker’s strongest properties—local determinism, explicit eligibility, route safety, and lifecycle updates only after real user-visible events.