Skip to content

Ask before announcing on Pubky: prompt on create / follow / clone, with a default-on Settings switch #39

Description

@jvsena42

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.ShareProfileui/util/ShareIntent.ktACTION_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.
  • Follow deck / clone deck do not exist yet — that is Split keeping someone else's deck into two features: Follow deck (read-only, receives updates) and Clone deck (copy into your account) #33. Only user-level follow exists
    (DiscoveryRepository.followUser/unfollowUser).
  • 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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. The follow/clone half depends on Split keeping someone else's deck into two features: Follow deck (read-only, receives updates) and Clone deck (copy into your account) #33. Neither action exists. Ship the create case first and
    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

  • 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

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.
  • Following a deck and cloning a deck each prompt with their own copy (once Split keeping someone else's deck into two features: Follow deck (read-only, receives updates) and Clone deck (copy into your account) #33 lands).

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.
  • 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.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions