Skip to content

Phase 2: End-user dashboard + custom destinations #6

Description

@rmichaelthomas

Overview

Phase 2 turns atshare.social from a marketing site into a user-facing product surface. Four features, all end-user-focused, all delivered via a new /dashboard route at atshare.social.

Full design doc and scope lock: atshare_checkpoint_phase2_v2.md (checkpoint artifact from design session April 15, 2026).

Features

1. Preference record view/edit — /dashboard/preference

The anchor feature. UI for viewing and editing social.atshare.preference directly, outside of any embedded selector. Drag-to-reorder priority, change primary network, add/remove destinations. Uses existing POST /api/preference route.

2. Custom destinations — /dashboard/destinations

Signed-in users can add destinations beyond the destinations.json registry (self-hosted PDSes, niche AppViews, personal Fediverse instances). Stored via Option B — new customDestinations field on the existing preference record. Escape hatch to Option C preserved via stable id per entry, mirroring schema, and repository pattern in code.

3. Sites I've used atShare on — /dashboard/sites

New social.atshare.usage record type. Selector writes a lightweight record to the user's PDS on first use per domain (check-then-write, fire-and-forget). Dashboard lists them with favicons (Google favicon service for MVP). Reinforces the "your preference traveled with you" value prop.

4. Delete all atShare records — /dashboard/settings

The no-lock-in promise made functional. One-click flow that removes every social.atshare.* record from the user's PDS, revokes OAuth session, clears localStorage. Confirmation modal with typed confirmation to prevent accidents. Server-side iterates KNOWN_COLLECTIONS const list (enforced by test to stay in sync with lexicons/).

Locked decisions

  • Storage: Option B (extend existing preference record with customDestinations) — backward compatible, one-fetch, escape hatch to Option C via stable id + repo pattern
  • Dashboard framework: Astro + React islands — matches current Vite tooling, zero-JS static pages, React only where interactive
  • Selector API: new max-destinations attribute, default 3 — site owner wins on quantity, user wins on identity/priority
  • Favicon source: Google favicon service for MVP
  • Share history / link tracking: dropped entirely, not deferred
  • Intent URL template: user-editable with tooltip in the add-destination modal
  • Lexicons: formalized in new lexicons/ directory as part of Phase 2
  • Delete-all ships in MVP alongside Features 1–3 — no-lock-in is a brand promise, not a later-phase bolt-on

Build order

Tracked as sub-tasks below. Each can become its own PR.

  • 1. Lexicon work

    • Create lexicons/ directory
    • Write formal lexicons/social.atshare.preference.json (reflects production shape + new customDestinations extension, minor version bump)
    • Create lexicons/social.atshare.usage.json
    • Create server/collections.js with KNOWN_COLLECTIONS array
    • Add test enforcing every file in lexicons/ has a matching KNOWN_COLLECTIONS entry
    • Publish both lexicons
  • 2. Repository pattern for custom destinations

    • Create src/custom-destinations-repo.js with list() / get(id) / upsert() / delete() methods
    • Initial implementation reads/writes customDestinations array on the preference record
    • Contract documented so Option C migration only changes this file
  • 3. Server-side route updates

    • Extend server/routes/preference.js to accept extended schema (including customDestinations)
    • Create server/routes/usage.js with list/create/delete endpoints
    • Create server/routes/account.js with DELETE /api/account — iterates KNOWN_COLLECTIONS, lists and deletes each record, revokes OAuth session
  • 4. Dashboard scaffolding

    • Scaffold Astro app at dashboard/ with @astrojs/react integration
    • Set up /dashboard route served from atshare.social
    • Reuse existing OAuth flow for auth gate (unauth'd users redirect to sign-in)
    • Share CSS tokens/theme with the existing landing page
  • 5. Feature 1: preference view/edit

    • dashboard/src/pages/preference.astro + PreferenceEditor.jsx island
    • Read/write via existing POST /api/preference route
    • Drag-to-reorder, add/remove, change primary
  • 6. Feature 2: custom destinations

    • dashboard/src/pages/destinations.astro + AddDestinationModal.jsx island
    • Add-destination modal with type/name/URL/intent-URL-template fields
    • HEAD-request validation step (warn but allow save on failure)
    • Intent URL template tooltip explaining auto-fill + placeholder reference ({text}, {url}, {title}, {instance})
    • Uses CustomDestinationsRepo; appends to both customDestinations and networks
  • 7. Selector component updates

    • Add max-destinations attribute (default 3)
    • Merge custom destinations into in-memory destination list at render time
    • Extend buildIntentUrl() in src/destinations.js to handle custom intentUrl templates
    • Silent degradation for unknown schema fields
    • Add usage record write on first use per domain (fire-and-forget, does not block share flow)
  • 8. Feature 3: sites list

    • dashboard/src/pages/sites.astro + UsageList.jsx island
    • Read usage records via new server route
    • Render list with favicons (Google favicon service) + dates
    • Per-row delete flow
  • 9. Feature 4: delete all atShare records

    • dashboard/src/pages/settings.astro + DeleteAccountFlow.jsx island
    • Confirmation modal with typed confirmation (e.g., type "DELETE")
    • Calls DELETE /api/account
    • Clears localStorage (atshare.handle, atshare.preference, etc.)
    • Redirects to sign-in with confirmation banner
  • 10. Testing

    • End-to-end on Recto (already embeds the selector)
    • Test with custom destinations configured
    • Test max-destinations behavior across registry + custom destinations
    • Backward compatibility: older selector reading a record with customDestinations should silently skip unresolvable entries
    • Delete-all flow: records actually removed from PDS, session revoked, user can sign in fresh
    • KNOWN_COLLECTIONS enforcement test: adding a lexicon file without updating the const fails CI

Lexicon extension (reference)

New customDestinations field on social.atshare.preference:

{
  "$type": "social.atshare.preference",
  "primaryNetwork": "atproto",
  "networks": [
    { "type": "atproto", "appView": "https://bsky.app" },
    { "type": "atproto", "appView": "https://alice.party" }
  ],
  "customDestinations": [
    {
      "id": "alice-party",
      "type": "atproto",
      "appView": "https://alice.party",
      "name": "Alice's PDS",
      "intentUrl": "https://alice.party/compose?text={text}"
    }
  ]
}

New social.atshare.usage record:

{
  "$type": "social.atshare.usage",
  "domain": "nytimes.com",
  "firstUsed": "2026-04-15T14:22:00Z"
}

Parked (not this phase)

  • Share history / link tracking — dropped entirely
  • Public profile page at atshare.social/@handle — deferred to later phase
  • Additional platforms — handled via destinations.json PRs
  • Site operator dashboard — separate product
  • Cross-user sharing of custom destinations — Phase 3+, enabled by the Option C escape hatch if/when needed

Risks

Risk Mitigation
Users confused about PDS usage records Onboarding copy emphasizing user control + delete-all as proof
Custom destination intent URLs don't work Validation step + user-editable template + tooltip
Older embedded selectors break on new records Option B silently degrades; verified via explicit test
Option B insufficient for future cross-user sharing Escape hatch preserved (stable id + repo pattern)

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions