Skip to content

Add social Markdown composer and XRPC thread publishing - #20

Merged
CountableNewt merged 12 commits into
devfrom
feature/skejkit-xrpc
Aug 24, 2026
Merged

CountableNewt merged 12 commits into
devfrom
feature/skejkit-xrpc

Conversation

@CountableNewt

Copy link
Copy Markdown
Contributor

Summary

  • preserve canonical social Markdown while compiling honest Bluesky text and UTF-8 facets in TypeScript and Swift
  • add accessible per-post Write and Preview editing, formatting commands, keyboard shortcuts, compiled validation, link cards, and legacy round trips
  • extend Skej schedule Lexicons and XRPC canonicalization with stable per-post rkeys, relationships, publication results, and duplicate reset behavior
  • publish sequential thread, reply, quote, and recordWithMedia records through com.atproto.repo.putRecord
  • add a shared golden corpus, Development seed records, React DOM coverage, SkejKit contract tests, and PDS/worker publication fixtures

Verification

  • bun run verify: lint, typecheck, 39 Lexicon tests, 49 web tests, 6 SkejKit tests, 82 gateway tests, and the Next production build passed; the macOS Swift release-build step reaches a reproducible Xcode-beta compiler assertion in third-party SwiftSoup Element.appendNormalisedText, also present at baseline
  • gateway Docker release build passed from services/skej-api/Dockerfile
  • git diff --check passed
  • local browser QA passed at desktop and 390x844 using seeded Markdown, Unicode, thread, reply, and quote schedules; Write and Preview projection, clickable facets, source restoration, and responsive controls were verified with no console warnings or errors

Risk and rollout

  • no deployment or environment promotion is included
  • top-level publication fields remain first-post compatibility aliases and legacy records without source remain literal plain text
  • Development seed data is restricted to the existing non-production seed procedure

No Skej Linear team or project is available in the connected workspace, so this PR is not linked to a Linear issue.

CountableNewt and others added 12 commits August 4, 2026 20:24
Deploy the web app on Railway
Remove the retired Fly deployment
The environment banner is an internal aid for telling local and dev
apart; it should never be shown to end users on skej.at.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix: hide environment banner in production

The environment banner is an internal aid for telling local and dev
apart; it should never be shown to end users on skej.at.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: gate Skej Pro features behind SKEJ_PRO_ENABLED

Teams, calendar, approvals, and posting on behalf of other accounts are
Skej Pro features still under development. One flag now gates them all:
on by default in local/test/dev, off in prod.

Gated endpoints do not exist when the flag is off: Pro-only routes are
never registered, non-self account DIDs throw the router's own
unknown-route error, and a draft status gets the same treatment as any
unrecognized status value, so nothing about the Pro surface is
fingerprintable. The web app learns the flag at runtime via /v1/me and
hides the calendar, account switcher, and team management cards.

Also fixes unknown routes returning 500 internal_error: ErrorMiddleware
now renders Hummingbird HTTPErrors with their real status, so unmatched
paths properly return 404 not_found.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Generate app.bsky.richtext.facet#tag facets next to link facets so
hashtags render as tag links on Bluesky. Detection mirrors @atproto/api:
a hashtag starts the text or follows whitespace, needs a character that
is not a digit, punctuation, or an invisible separator, trims trailing
punctuation, and caps the tag at 64 characters.

Stale link and tag facets sent by a client are now regenerated from the
text rather than preserved, so byte offsets cannot drift after an edit.
Already-scheduled posts pick up tag facets at publish time through the
shadow record path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Listing schedules resolves brand permissions first, which walks every
managed account and calls listRecords for each. authenticatedSession sat
outside the do/catch that falls back to cached records, so any account
without an OAuth session row threw notConfigured and the request became
an opaque 500 -- for every user, not just the account at fault. Dev hits
this because dev/seed inserts six accounts with no OAuth sessions.

Move the session lookup inside the fallback in listRecords and getRecord
so a missing or rejected credential reads through to the local cache, the
same way an unreachable PDS already did.

Because reads now degrade quietly, add the detection that makes that safe:
PDSClientError.isAuthFailure is the single definition of "credentials are
gone" (shared with ScheduleWorker.classify so they cannot drift), and a
read that falls back for auth reasons marks the account needs_reauth.
Transient failures deliberately do not.

Writes still cannot succeed without credentials, so map auth failures to
409 account_needs_reauth instead of a 500. Not 401 -- the Skej session is
valid, only the PDS credential is dead, and a 401 reads as "signed out".

ErrorMiddleware also logs unhandled errors now; it swallowed them
silently, which is why this surfaced as a bare 500 with nothing to go on.

Surface all of this in the UI, since needs_reauth was previously set only
by the worker and read by nothing. requestJSON throws SkejApiError
carrying the error code, a banner names the stale account and offers a
Reconnect that starts OAuth with the handle prefilled, both account
pickers mark stale accounts, and the settings page gains a status badge
and its own reconnect card.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Moves shared scheduling contracts and services into SkejKit while exposing product operations through typed Lexicon-backed XRPC procedures.
Adds the shared golden corpus, lossless Markdown source models, stable per-post publication references, and the dependency-free Swift compiler.
Provides per-post Write and honest Bluesky Preview modes, selection-aware formatting, canonical source round trips, and shared compiler/UI fixtures.
Authoritatively recompiles post plans, assigns retry-stable record keys, publishes ordered reply and quote records, and persists complete publication summaries.
Allow thread cards, editor panels, and textareas to shrink within the 390px layout so all post relationship controls remain reachable.
# Conflicts:
#	services/skej-api/Sources/SkejGateway/HTTP/HTTPHelpers.swift
#	services/skej-api/Sources/SkejGateway/OAuth/ATProtoOAuthClient.swift
#	services/skej-api/Sources/SkejGateway/Routes/AppRouter.swift
#	services/skej-api/Sources/SkejKit/Posts/PostRecordCanonicalizer.swift
#	services/skej-api/Tests/SkejKitTests/ATProtoPDSClientTests.swift
#	services/skej-api/Tests/SkejKitTests/HTTPHelpersTests.swift
#	services/skej-api/Tests/SkejKitTests/PostRecordCanonicalizerTests.swift
#	services/skej-api/Tests/SkejKitTests/ProFeatureGateTests.swift
@CountableNewt
CountableNewt merged commit 34aa887 into dev Aug 24, 2026
5 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