Skip to content

Web parity batch 2: DMs, Moderation, Sharing, Search, List Folders, Templates, X cross-post, profiles - #6

Merged
Adron merged 20 commits into
mainfrom
feature/web-parity-batch-2026-07
Aug 1, 2026
Merged

Web parity batch 2: DMs, Moderation, Sharing, Search, List Folders, Templates, X cross-post, profiles#6
Adron merged 20 commits into
mainfrom
feature/web-parity-batch-2026-07

Conversation

@Adron

@Adron Adron commented Aug 1, 2026

Copy link
Copy Markdown
Member

Web parity — batch 2

Closes the highest-priority feature gaps between the macOS native app and the live web app (interlinedlist.com), each built backend-first against live-verified API shapes and wired end-to-end (Kit → Domain → App) with BDD tests. Continues after the merged PR #5.

Features shipped (9)

Gap What it adds
G1 Direct Messages Inbox/Sent/Deleted folders, threads with live polling, compose + recipient picker, profile "Message" action, unread dock badge (summed with notifications via UnreadBadgeAggregator)
G2 Moderation Block / mute / report from timeline & profile menus; Settings → "Blocked & Muted" pane (also removed the last AppKit dependency from MessageRowView)
G3 Share Links Create/list/revoke tokenized view/edit/admin links for lists & documents; resolve + claim via interlinedlist://…/shared/{token} and pasted URLs
G5 Search Global ⌘F search across messages, lists, documents
G6 List Folders Subscriber-gated hierarchical folder tree in the Lists sidebar
D2 Public profile Adopts the real GET /api/users/{username} (bio, follower/following counts, join date, private flag), replacing the decision-0002 embedded-author fallback
G7 X/Twitter cross-post Additive mirror of the Bluesky/LinkedIn path (composer toggle + crossPostToTwitter)
G12 Server templates "Your templates" section in the New-from-Template picker (create-from-template + seed-defaults)
G11a LinkedIn targets Backend for reading LinkedIn posting targets (composer picker UI = follow-up)

Tests

All green, 0 regressions:

  • InterlinedKit 224 → 274 · InterlinedDomain 475 → 546 · App 379 → 502 · InterlinedPersistence 120 (unchanged)
  • ~244 new BDD tests. SPM packages via swift test; App target via xcodebuild test CODE_SIGNING_ALLOWED=NO (ad-hoc-vs-team-signed test-host quirk on the build machine — worth a project-level fix).

Docs

  • the-gaps.md — consolidated master parity doc (live-verified gap list, plan, progress table); supersedes feature-gaps.md.
  • docs/api-coverage.md — re-baselined to the live ~151-endpoint surface (was stale at 98); public-profile & OAuth-linking footnotes resolved.

Out of scope

  • In-app billing (G8) — managed in the online app (owner decision); the native app only reads customerStatus for feature-gating. (Stripe routes were also 404/not-deployed.)

Verified-but-blocked (need backend/infra, not native work)

  • Following feedGET /api/messages ignores feed/scope params; viewingPreference update 405s.
  • Multi-account/api/auth/accounts is session-cookie-only (401 under Bearer).
  • Push (APNs) — needs an APNs entitlement under the .pkg model.
  • LinkedIn org pages (G11b) — routes 404/not-deployed.
  • GitHub issues (G4) — routes live but need a linked GitHub identity to verify shapes (build pending a decision: link an identity, or build on GitHub's standard shapes + flag).

Method note

Wire shapes were verified against the live API using the dedicated .env test account — read-only probes throughout, plus a few reversible recon writes (one DM, one share link, one from-template doc) each created and then cleaned up.

Not included

The in-progress Document Sync Agent (SyncAgent/) and the blog-post/ assets are a separate feature and were intentionally left out of this PR (staged locally, unrelated to web parity).

🤖 Generated with Claude Code

Adron and others added 20 commits July 18, 2026 13:27
Extends the schema DSL with select(a|b|c) single-choice columns (options on
SchemaField.enumValues) and markdown long-text columns. Editor gains an inline
options editor; row cells render a Picker (select) and a TextEditor+Textual
preview (markdown). 25 new BDD tests. Reaches parity with the site's advertised
type set (feature-gaps.md §1.1). SwiftUI-only; no wire/codec change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The server already returned linkMetadata (title/description/image); the domain
model dropped it. Adds Message.linkPreviews + a mapper that drops unparseable
URLs, and a tappable LinkPreviewCardView (AsyncImage thumbnail + title + host)
in the timeline. Render gate is forward-compatible pending fetchStatus docs.
16 new tests (feature-gaps.md §1.5). Not persisted in SwiftData by design.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Client-side template catalog (Blank / Meeting Notes / Daily Log / PRD) with a
New-from-Template command (Shift-Opt-Cmd-N) and picker sheet that seeds the
markdown body through the existing create path. Blank == prior behavior.
12 new tests (feature-gaps.md §1.4). No templates endpoint exists; static
catalog can move server-side later.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pure renderer for documents, message threads, and lists-as-tables (schema-ordered
columns, pipe/newline-escaped cells). Uses value-type Date.ISO8601Format for
Sendable safety. 15 BDD tests. Client-side because /api/exports/* is CSV-only
(see feature-blockages.md NB-3). UI wiring lands next (feature-gaps.md §1.3).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ed code

Adds feature-gaps.md (parity review) and feature-blockages.md (backend asks,
reconciled against blocker-prompts.md). Corrects 5 stale feature-status.md
limitations (scheduled cancel/reschedule, watcher/org invite-by-handle,
cross-post readiness, following scope) and de-stales two Help Book pages +
regenerates the help index. Verified against current code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds P1-G (following feed), P1-H (GitHub issue writes), P2-F (Markdown export),
P2-G (schema DSL select/markdown), P3-F (link-preview fetchStatus), P3-G (list
clone), P3-H (PATCH/PUT) with paste-ready prompts. feature-blockages.md is now a
concise index into the canonical blocker-prompts.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds an 'Export My Lists as Markdown' path: ExportViewModel paginates owned
lists + rows and renders them via MarkdownExporter into a MarkdownFileDocument
(.md), surfaced through a second fileExporter and a Markdown button on the
Lists row. 4 new tests (happy/empty/failure/pagination). Completes feature-gaps
§1.3 (engine + UI).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the List-of-cells fallback in tableMode with a real SwiftUI Table
using TableColumnForEach for one typed column per schema field (now valid at
the macOS 15 deployment target — the 14.4 fallback comment was stale).
Pagination becomes a Load-More footer since Table has no per-row appearance
hook; cards mode keeps scroll-to-load. Completes feature-gaps §1.2 (grid; ERD
still scoped separately).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…end + UI)

Close four top-priority web-parity gaps end-to-end against live-verified API
shapes, and consolidate the parity docs.

Features (Kit endpoints+DTOs → Domain services+mappers → SwiftUI MVVM + tests):
- G5 Search: GET /api/{messages,lists,documents}/search; global ⌘F search UI
  reusing existing Message/List/Document mappers.
- G2 Moderation: block/mute/report + isBlocking; timeline/profile overflow-menu
  actions + Settings "Blocked & Muted" pane. Removes the last AppKit dependency
  from MessageRowView.
- G6 List Folders: /api/folders CRUD (subscriber-gated create, cycle-safe tree
  builder); Lists sidebar folder tree.
- G1 Direct Messages: /api/dm folders/thread/send/recipients/unread + read/
  trash/restore; inbox/thread/compose UI with threadUpdates polling, a
  recipient-gated profile "Message" action, and an UnreadBadgeAggregator that
  sums DM + notification unread counts. (SwiftData cache deferred — needs a
  domain-side store seam.)

Docs:
- the-gaps.md: consolidated master parity doc (live-verified gap list, plan,
  and progress table); supersedes feature-gaps.md.
- docs/api-coverage.md: re-baselined to the live ~151-endpoint surface; resolved
  the public-profile (fn8) and native-OAuth-linking (fn12) footnotes.

Wire shapes verified live 2026-07-31 (read-only probes; DM object confirmed via
one authorized recon message that was then trashed).

Tests: InterlinedKit 224→250, InterlinedDomain 475→522, App 379→452
(+146 new, 0 regressions).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tokenized share links for lists and documents against live-verified shapes
(captured via a reversible create→capture→revoke on the test account).

- Kit: `Sharing` endpoint + DTOs — create/list/resolve/revoke/claim for both
  lists (`/api/lists/{id}/share-links`, `/api/lists/shared/{token}`) and
  documents (parallel paths).
- Domain: `SharingService` (subscriber-gated create via EntitlementsService),
  `ShareRole` (watcher/collaborator/manager ↔ Viewer/Editor/Admin), `ShareLink`,
  `ResolvedShare` (list/document resource), `ShareClaim`.
- Tests: +18 (7 Kit + 11 Domain). InterlinedKit 250→257, InterlinedDomain
  522→533, 0 regressions.

Per-person collaborator grants (documents) remain a follow-up. App-layer Share
sheet + resolve/claim landing to follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ing)

- Share Links panel (ShareLinksView/ViewModel over a ShareTarget list|document):
  create with role + optional expiry, list active links, revoke (optimistic),
  subscriber-gate → upsell. Surfaced from the Lists and Documents toolbars.
- Shared-resource landing (ResolveShareView/ViewModel): resolve a token, show
  resource + role, claim when eligible; ownership-gated (hides claim / prompts
  sign-in when no current user).
- ShareURLParser (pure) + ShareLinkDeepLink: recognizes …/lists|documents/
  shared/{token} across https and interlinedlist://; called before the OAuth
  callback path in onOpenURL and returns false for non-share URLs.
- Clipboard without AppKit via SwiftUI.ShareLink; AppEnvironment.sharing rebuilt
  per-access with live entitlements (mirrors listFolders).

Tests: +38 (ShareLinksViewModel 12, ResolveShareViewModel 11, ShareURLParser 15).
App target 452→490, 0 failures. Packages unchanged (Kit 257, Domain 533,
Persistence 120). Decision-0003 grep gate clean.

Follow-ups: per-person collaborator grants; in-app sign-in from the landing;
post-claim deep-select of the specific resource.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…back)

`GET /api/users/{username}` now exists (verified live 2026-07-31) and is
strictly richer than the embedded-author fallback.

- Kit: `PublicProfileDTO` + `User.publicProfile(username:)`.
- Domain: `UserProfile.init(from: PublicProfileDTO)` (bio, follower/following
  counts, joinedAt, isPrivate straight from the payload — no counts stitch).
- `SocialService.profile(username:)` now prefers the real endpoint; the
  decision-0002 message-embedded-author fallback is retained only when the
  endpoint 404s (pre-migration servers). Non-404 errors propagate.
- The existing profile UI renders the richer fields with no view changes.

Tests: +8 (2 Kit builder + rewired SocialService profile suite). InterlinedKit
257→259, InterlinedDomain 533→534, 0 regressions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…LinkedIn)

Extends the existing Mastodon/Bluesky/LinkedIn cross-post feature to X/Twitter.
Strictly additive — every pre-existing cross-post test stays green.

- Kit: `.twitter` OAuthProvider (slug verified live), `Auth.twitterStatus()` +
  `TwitterStatusResponse`, and `crossPostToTwitter: Bool?` on CreateMessageRequest.
- Domain: `MessagesService.createPost` gains a `crossPostToTwitter` param, folded
  into the `.crossPosting` entitlement gate and the request body.
- App: X toggle in the composer cross-post section (mirrors LinkedIn), threaded
  through submit + result summary.

⚠️ The `crossPostToTwitter` wire field name is PATTERN-MATCHED to the confirmed
crossPostToBluesky/crossPostToLinkedIn naming but UNVERIFIED against live
POST /api/messages (the test account has no linked X identity). If the server
uses `crossPostToX`, only MessageDTO.CodingKeys needs to change — flagged in a
doc comment on the field.

Tests: +12. InterlinedKit 259→265, InterlinedDomain 534→536, App 490→492,
Persistence 120 unchanged. 0 regressions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The user's saved server-side templates (distinct from the client built-in
catalog), verified live 2026-07-31 (from-template shape captured via a
reversible create+delete).

- Kit: additive `Documents` extension — `templates()`, `createFromTemplate()`
  (body `{templateDocumentId}`), `seedDefaultTemplates()` + DTOs.
- Domain: `DocumentTemplatesService` (own protocol, so no existing
  DocumentsServicing conformance changes) + `DocumentTemplateRef`.
- Tests: +11 (5 Kit + 6 Domain). InterlinedKit 265→270, InterlinedDomain
  536→542, 0 regressions.

Also documents live-API blockers found this session: Stripe billing routes and
org LinkedIn pages return 404 (not deployed); GitHub routes need a linked
identity. App-layer template picker + /api/documents/tree = follow-ups.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surfaces the user's server-side templates in the existing "New from Template"
flow as a second "Your templates" section alongside the built-in catalog.

- `ServerTemplatesViewModel` (@mainactor) over `DocumentTemplatesServicing`:
  load + map templates, non-fatal load error, empty state with a "Seed
  defaults" affordance, create-from-template (debounced) → reload docs → select
  the new document.
- `DocumentTemplatePickerView` now renders two sections under a unified
  TemplateSelection (.builtIn/.server); built-in behavior unchanged.
- `DocumentTemplatesService` registered in AppEnvironment.

Tests: +10 (ServerTemplatesViewModelTests). App target 492→502, packages
unchanged (Kit 270, Domain 542, Persistence 120). 0 regressions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Owner decision 2026-07-31: billing stays in the online app; the native app
never sells/manages subscriptions (it still reads customerStatus to gate
subscriber features). G8 marked OUT OF SCOPE across the-gaps.md and
api-coverage.md; Stripe rows dropped (also 404/not-deployed live). No code
change — no in-app billing was ever built.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Read the user's LinkedIn cross-post destinations so the composer can offer a
target picker. Shapes verified live 2026-07-31.

- Kit: `LinkedIn.postingTargets()` / `LinkedIn.targets()` + DTOs.
- Domain: `LinkedInService.postingTargets()` → `LinkedInPostingTargets`
  (targets + orgScopeMissing); `LinkedInTarget` (personal/org/other).
- Tests: +8. InterlinedKit 270→274, InterlinedDomain 542→546, 0 regressions.

Org pages (G11b) remain deferred (routes 404/not-deployed). Composer target
picker UI to follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ocked)

Live probe 2026-07-31: GET /api/messages ignores feed/scope/following/filter
params (all return the 216-total "all" feed); POST /api/user/update
{viewingPreference} → 405. No client-side following-feed path exists.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Adron
Adron merged commit e638e05 into main Aug 1, 2026
4 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant