You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Creating a deck, following a deck, or cloning a deck are all social moments, and none of them
currently reach anyone. Ask the user "Share this on Pubky?" after each one, announce it to their
social graph when they say yes, and let a default-on switch in Settings control whether the
question is asked at all.
Terminology matters here. Publishing a deck already writes it publicly to the homeserver —
there are no private decks in v1 (spec §11), so "share" cannot mean visibility. It means announcing: writing a pubky.app post so the people following you see that the deck exists.
That distinction has to survive into the copy, or the switch reads as a privacy control it is not.
Current state (all confirmed in code)
Settings has no toggles at all.SettingsUiState is four read-only fields (pubky, displayName, homeserver, appVersion); SettingsViewModel exposes only onCopyPubkyClick
and onSignOutClick. There is no section for preferences to live in.
There is no preference storage. The only persistence in the app is SecureSessionStore
(Liftric KVault → Keystore/Keychain), which exists for the signed-in Session and is documented
as the secrets path. No DataStore, no multiplatform-settings — grep for either returns nothing.
A boolean setting has nowhere to go today.
"Share" today is the OS share sheet, not Pubky.DeckDetailEffect.Share / ProfileEffect.ShareProfile → ui/util/ShareIntent.kt → ACTION_SEND with a pubky:// URI.
Nothing is written to the homeserver.
There is no post primitive.PubkyClient exposes createTagId and nothing post-shaped; the
generated FFI has create_tag_id only (grep create_post_id → zero hits). PubkyPaths has followsRoot/follow/tag but no posts path.
Two entry points create a deck, both landing on DeckRepository.publish(deck, cards): PublishDeckViewModel.publish() (paste-import) and DeckEditorViewModel.save() (manual editor).
Blockers found while scoping
No non-secret preference store. This must be built before the switch can persist. A shared AppPreferences interface in data/storage/ alongside SecureSessionStore, with expect/actual
impls, is the smallest thing that fits the existing layering. Secondary decision: device-local, or
synced to the homeserver (e.g. /pub/loopky/settings.json) so a reinstall or a second device keeps
the choice? Local is fine for v1 but the record path should be reserved now if we want sync later.
Post ids are not derivable through the current FFI. pubky-app-specs posts live at /pub/pubky.app/posts/{id} with a timestamp-based Crockford-base32 id, and the fork exposes no
helper. Either add one to pubky-core-ffi-fork (regeneration steps in Architecture.md §7.4) or
hand-roll the id in shared — and if hand-rolled, it must match exactly what Nexus validates or
the post is written and never indexed. TagRepositoryImpl is the precedent for writing a
pubky.app-native record from Loopky; follow its shape.
DeckEditorViewModel.save() is not a "create" signal. It republishes the whole manifest on
every edit of an existing deck. Announcing from the save path naively means a post every time
someone fixes a typo. Only the first publish of a new deckId may announce.
The import flow has a 10s undo window.PublishDeckViewModel keeps UNDO_WINDOW_SECONDS = 10
during which onUndoClick deletes the deck. Announcing before that elapses posts about a deck
that may be gone a second later. Post after the window resolves, never inside it.
AppPreferences in shared/commonMain/.../data/storage/, exposing shareOnPubky: Flow<Boolean> (default true) + suspend fun setShareOnPubky(enabled: Boolean).
Platform actuals bound in PlatformModule.{android,ios}.kt, same as SecureSessionStore.
Semantics (decided): on → ask after each qualifying action; off → never ask, never
post. Off is a full opt-out of the feature, not a "post silently" mode — nothing reaches the
homeserver while it is off.
Announcing
Extend DiscoveryRepository — it already owns the social primitives — with announceDeck(deck, kind) where kind ∈ Created | Followed | Cloned. No use-case layer, and
nothing deck-shaped goes into PubkyClient.
Announcing is best-effort. A failed post must never fail or roll back the create/follow/clone
— log it, surface a toast at most.
Prompt
After a successful create (post-undo-window), follow, or clone: a confirm sheet with the composed
post preview, Not now / Share on Pubky, and a "Don't ask again" affordance that flips the
Settings switch off so the two controls are visibly the same setting.
For the import flow, fold this into the existing publish-success screen rather than stacking a
dialog on top of it.
Settings
New "Sharing" section with a Material 3 Switch row, on by default; shareOnPubky in SettingsUiState, onShareOnPubkyChange(Boolean) on the VM. Subtitle copy must say it controls announcing to your followers, not deck visibility.
Acceptance criteria
A boolean preference persists across app restarts through a shared AppPreferences, not through SecureSessionStore.
Settings shows a "Share on Pubky" switch, on by default, and toggling it survives a restart.
Creating a deck from paste-import prompts after the undo window elapses; undoing within the window prompts nothing and posts nothing.
Creating a deck from the editor prompts once; editing that deck afterwards never prompts and never posts.
Accepting the prompt writes a pubky.app post that Nexus indexes and that resolves back to the deck's pubky:// URI.
Declining the prompt writes nothing; "Don't ask again" turns the Settings switch off.
With the switch off, no prompt appears for any of the three actions and no post is written.
A failed announce leaves the deck created / the follow or clone intact, with the error surfaced non-fatally.
Switch off — never ask and never post, or ask-suppressed-but-post-silently? Decided: off means never ask and never post. No tri-state.
One switch, or per-action granularity (create / follow / clone)? Following and cloning announce
someone else's deck, which some users will want quieter than announcing their own.
Post record type: a pubky.app post (indexed by Nexus, visible to every Pubky app) or an
loopky-namespaced record (invisible outside Loopky)? Only the former makes this worth building.
Does deleting a deck delete or amend its announcement post? Unfollowing after a follow post?
Should the existing user-level followUser also prompt? Proposed out of scope.
Does the prompt belong on the first deck only, with the switch discovered from there — or every
time, as asked? Every-time is the ask; worth watching for prompt fatigue on bulk imports.
Creating a deck, following a deck, or cloning a deck are all social moments, and none of them
currently reach anyone. Ask the user "Share this on Pubky?" after each one, announce it to their
social graph when they say yes, and let a default-on switch in Settings control whether the
question is asked at all.
Terminology matters here. Publishing a deck already writes it publicly to the homeserver —
there are no private decks in v1 (spec §11), so "share" cannot mean visibility. It means
announcing: writing a
pubky.apppost so the people following you see that the deck exists.That distinction has to survive into the copy, or the switch reads as a privacy control it is not.
Current state (all confirmed in code)
SettingsUiStateis four read-only fields (pubky,displayName,homeserver,appVersion);SettingsViewModelexposes onlyonCopyPubkyClickand
onSignOutClick. There is no section for preferences to live in.SecureSessionStore(Liftric KVault → Keystore/Keychain), which exists for the signed-in
Sessionand is documentedas the secrets path. No DataStore, no multiplatform-settings —
grepfor either returns nothing.A boolean setting has nowhere to go today.
DeckDetailEffect.Share/ProfileEffect.ShareProfile→ui/util/ShareIntent.kt→ACTION_SENDwith apubky://URI.Nothing is written to the homeserver.
PubkyClientexposescreateTagIdand nothing post-shaped; thegenerated FFI has
create_tag_idonly (grep create_post_id→ zero hits).PubkyPathshasfollowsRoot/follow/tagbut no posts path.(
DiscoveryRepository.followUser/unfollowUser).DeckRepository.publish(deck, cards):PublishDeckViewModel.publish()(paste-import) andDeckEditorViewModel.save()(manual editor).Blockers found while scoping
AppPreferencesinterface indata/storage/alongsideSecureSessionStore, withexpect/actualimpls, is the smallest thing that fits the existing layering. Secondary decision: device-local, or
synced to the homeserver (e.g.
/pub/loopky/settings.json) so a reinstall or a second device keepsthe choice? Local is fine for v1 but the record path should be reserved now if we want sync later.
/pub/pubky.app/posts/{id}with a timestamp-based Crockford-base32 id, and the fork exposes nohelper. Either add one to
pubky-core-ffi-fork(regeneration steps in Architecture.md §7.4) orhand-roll the id in shared — and if hand-rolled, it must match exactly what Nexus validates or
the post is written and never indexed.
TagRepositoryImplis the precedent for writing apubky.app-native record from Loopky; follow its shape.
DeckEditorViewModel.save()is not a "create" signal. It republishes the whole manifest onevery edit of an existing deck. Announcing from the save path naively means a post every time
someone fixes a typo. Only the first publish of a new
deckIdmay announce.PublishDeckViewModelkeepsUNDO_WINDOW_SECONDS = 10during which
onUndoClickdeletes the deck. Announcing before that elapses posts about a deckthat may be gone a second later. Post after the window resolves, never inside it.
wire the other two as Split keeping someone else's deck into two features: Follow deck (read-only, receives updates) and Clone deck (copy into your account) #33 lands, rather than blocking this behind it.
Proposal
Preference
AppPreferencesinshared/commonMain/.../data/storage/, exposingshareOnPubky: Flow<Boolean>(defaulttrue) +suspend fun setShareOnPubky(enabled: Boolean).Platform
actuals bound inPlatformModule.{android,ios}.kt, same asSecureSessionStore.post. Off is a full opt-out of the feature, not a "post silently" mode — nothing reaches the
homeserver while it is off.
Announcing
DiscoveryRepository— it already owns the social primitives — withannounceDeck(deck, kind)wherekind∈Created | Followed | Cloned. No use-case layer, andnothing deck-shaped goes into
PubkyClient.pubky.apppost viaputWithSessionRetry, body composed from the verb, deck title andpubky://URI. A clone credits the original author (pairs withcloned_fromfrom Split keeping someone else's deck into two features: Follow deck (read-only, receives updates) and Clone deck (copy into your account) #33).— log it, surface a toast at most.
Prompt
post preview,
Not now/Share on Pubky, and a "Don't ask again" affordance that flips theSettings switch off so the two controls are visibly the same setting.
dialog on top of it.
Settings
Switchrow, on by default;shareOnPubkyinSettingsUiState,onShareOnPubkyChange(Boolean)on the VM. Subtitle copy must say it controlsannouncing to your followers, not deck visibility.
Acceptance criteria
AppPreferences, not throughSecureSessionStore.pubky.apppost that Nexus indexes and that resolves back to the deck'spubky://URI.Open questions
Switch off — never ask and never post, or ask-suppressed-but-post-silently?Decided: off means never ask and never post. No tri-state.
someone else's deck, which some users will want quieter than announcing their own.
pubky.apppost (indexed by Nexus, visible to every Pubky app) or anloopky-namespaced record (invisible outside Loopky)? Only the former makes this worth building.
followUseralso prompt? Proposed out of scope.time, as asked? Every-time is the ask; worth watching for prompt fatigue on bulk imports.
Related