Imported from masumi-network/sokosumi (
apps/apple/AGENTS.md). Install upstream withnpx skills add masumi-network/sokosumi --skill apple. Copyright stays with the author.
Sokosumi Apple Clients Agent Guidelines
Purpose: This document provides app-specific guidelines for AI agents working on the native macOS/iOS clients. For comprehensive monorepo guidelines, see the root AGENTS.md. Before Apple work, read
VISION.mdfor the ongoing goal andPARITY.mdfor the authorized scope, iteration rules, current PR, and verification record. Verify the handoff against GitHub before continuing. The next parity row runs in its own session, started with/apple-parity-next;node apps/apple/scripts/parity-status.mjsanswers what is open, stale and next.
App-Specific Architecture
Stack: One Xcode workspace (Sokosumi.xcworkspace) holding Sokosumi.xcodeproj (product Sokosumi) and the five local packages as root packages, so the Sokosumi scheme runs the app tests and every package suite in one command. Build and test through the workspace, not the project — -project cannot reach the package test targets. macOS target first, shared Swift packages under Packages/ (first: CoreAPI, generated via Swift OpenAPI Generator). No iOS target yet; packages must stay free of AppKit/SwiftUI so their domain behavior remains extractable for future native clients. No package.json. Xcode is outside turbo and Biome. Swift tooling (SwiftLint, SwiftFormat) installs via Mint with exact pins in Mintfile, not Homebrew directly.
Key directories: Sokosumi/ (thin SwiftUI app: auth composition/browser adapter, workspace/chat composition, views), Packages/CoreAPI/ (generated Core HTTP client), Packages/SokosumiAuth/ (portable auth state, OAuth session and the TokenStore port), Packages/SokosumiChat/ (portable WorkspaceSession/ConversationSidebar/RoomTimeline, avatar loading, scoped room/draft persistence, rooms/chat flows), Packages/SokosumiRealtime/ (Ably transport adapter, domain event delivery, room subscriptions, org presence and the open room's typing channel), Packages/SokosumiWorkspace/ (portable cross-package coordinator and integration tests), SokosumiTests/ (app-target tests).
Source navigation
The Xcode navigator mirrors the real folders under Sokosumi/ using filesystem-synchronized groups. Add Swift files to the owning folder; do not add parallel virtual groups or manual build-file entries.
App/: app scenes, the Sparkle updater adapter (AppUpdater),ChatRootView, the selected room's detail stack with an open Thread pushed over it (RoomNavigationStack), endpoint configuration and the Mac presence activity monitor.Authentication/: sign-in presentation, app OAuth configuration, system-browser adapter and Keychain adapter forTokenStore.Packages/SokosumiWorkspace/: shared workspace, room and realtime coordination; thread orchestration is inWorkspaceState+Threads.swift.Chat/Sidebar/: conversation sections, workspace/account menus and room labels. The rows (emphasis, status, badge, mark), the room menu (its entries, order and enable state, shared with the Members inspector) the toolbar's enable flags and which sections stand (visibility, empty state, a closed heading's attention) come fromSidebarPresentation(SidebarRoomRow,RoomMenu,SidebarCommands) andSidebarSectionsinSokosumiChat; the app draws them and owns every title and symbol (RoomMenuEntryLabel).Chat/Timeline/: room scrolling, message rows, timeline status rows, the jump mark's drawing (JumpMarkBackground) and the spotlight it casts over the list's other rows (JumpSpotlightClock), the Seen by faces and list on the newest message (SeenByButton) and the room header in the title bar, whose name is a button (RoomHeaderModifier). The mark's clock isJumpMarkand the spotlight's step-backJumpSpotlight, the header's mark, name and topic come fromRoomHeaderIdentity, who has read how far comes fromRoomReadReceipts, and what each message row offers and draws comes fromMessageRow(itsMessageKindand the transcript'sTranscriptContext), all inSokosumiChat.Chat/Threads/: reply-thread presentation.Chat/Pins/: pinned-message inspector and preview cards; pin state and networking stay in the shared packages.Chat/Search/: Room Find toolbar, shared inspector presentation and search result rows.Chat/Details/: room details/members inspector (add, remove, leave), its Add picker with the host-side guest access tab, the admin-only channel settings sheet, the window's one Channel settings and Name Group sheet (RoomEditSheets) and lifecycle confirmations; membership rules, guest state and networking stay in the shared packages.Chat/Invitations/: channel invitation and guest join-link sheets; invitation state and networking stay in the shared packages.Chat/Composer/: draft-owning rich composer, the Typing line under the room composer (RoomTypingLine), Drive picker (DriveFilePickerView) and isolated native text input. Typing state and its channel stay in the shared packages.Chat/Rendering/: Markdown, code, expansion, coworker thought, Mermaid flowcharts (MermaidFigureView;MermaidRendereris the only caller of BeautifulMermaid, behind web's policy inSokosumiChat.MermaidDiagram), attachment chips/previews (MessageAttachmentView,MessageImageViewer,DocumentAttachmentPreview,NativeOfficePreview) and Soko Bot result cards (MessageResultPreviewsView,ResultPreviewCardView; the read and card model areMessageResultPreviewsinSokosumiChat) with the Task card (ResultTaskCardView; its model isResultTaskCardinSokosumiChat), the social post preview (ResultSocialPostView; its model isResultSocialPostinSokosumiChat) and their protected outputs (ResultOutputsView, with Quick Look, the save panel and the loader inResultOutputPresenter; the href parsing and authorized load areResultOutputsandChatService.resultOutputinSokosumiChat), with the project question's picker and the reply's chip (ProjectSelectionView; the reply format and the verified send areProjectSelectioninSokosumiChat) and the decision card (DecisionCardView; the proposal summary, the tool label and the resolve call areProposalSummary,SokoBotDecisionandChatService.resolveSokoBotDecisioninSokosumiChat).Shared/: reusable participant avatar,PresenceDot,ParticipantProfileButton,ParticipantDetailsViewand the one symbol per Channel mark (ChannelMark.systemImage; the mark itself is portable). Avatar networking and presence state remain inSokosumiChat.Settings/: Settings scene content and thetimeFormatenvironment value; preference state and networking stay in the shared packages.Notifications/:UNUserNotificationCenteradapter (ChatNotificationCenter) and its lifecycle modifier for local chat banners while the app runs. The banner rules, preferences and navigation stay in the shared packages; no push registration.
SokosumiTests/ mirrors the relevant feature folders. Shared packages keep their existing platform-agnostic ownership. Name files after their main type; use role-specific names rather than generic ContentView or helper buckets. Extract independent views without changing their state identity or widening private orchestration state just to shorten a file.
Ownership rules
Read README.md for the dependency map and source navigation. Keep it synchronized with ownership and setup changes; keep live feature/PR status only in PARITY.md.
- Views own layout, focus, hover and transient editing state. Dispatch chat actions through the coordinator; do not add HTTP requests or token handling to views.
WorkspaceStatecomposes package lifecycles. Reusable room/thread behavior belongs inSokosumiChat, auth inSokosumiAuth, and socket transport inSokosumiRealtime. The coordinator lives inSokosumiWorkspace; inject app configuration through its client provider rather than importing the app target or putting orchestration in a transport package.- Preserve app-owned auth/workspace objects, per-window visibility identities and per-room/thread draft identities during extraction. Do not recreate these objects in child views.
- Keep cancellation and generation guards with the operation they protect. Do not widen private access merely to split an extension into another file.
- Package code must never import the app target or UI frameworks. Parsing produces portable models; SwiftUI rendering consumes them.
- For structural changes, run the same app/affected-package tests before and after. Verify the diff for changed defaults, state lifetime, async ordering, access control and generated/project configuration.
Portable domain / future clients
- Packages own the domain, not only preparation for iOS. Public domain models must not mention Keychain,
UNUserNotificationCenter,ASWebAuthenticationSession,NSWorkspace, orably-cocoatypes. - Token persistence uses the
TokenStoreport. Keychain belongs in the app target or an Apple adapter, never in domain types. Auth package tests use in-memory stores. SokosumiRealtimetranslates transport deliveries into domain events (ResolvedRealtimeDelivery);SokosumiChatapplies message, thread and presence rules. Chat/Workspace public domain APIs must not expose SDK connection objects or Ably types. Keep the transport-neutralRealtimeConnectionfactory at the composition boundary only.- Every new domain rule needs a UI-free test: given state + event → new state. Golden fixtures live in
SokosumiChatTests/Fixtures/Golden; use fixed time and IDs. A slice changing domain behavior is incomplete if it only adds app/UI tests. - Do not grow
WorkspaceStateinto a god object. New behavior belongs in Chat/Auth/Realtime first; Workspace composes their lifecycles. - Inject a
Clockor time provider and ID generation into domain code. No hiddenDate()or UUID generation inside reducers. - Local persistence schemas are versioned contracts with one migration site per schema. Do not add ad-hoc
UserDefaultsaccess in views. - Core OpenAPI remains the only DTO source. Do not hand-copy “almost Core” structs.
- Keep
#if os(macOS)out of packages except a documented, tiny compatibility shim. - Keep the SwiftUI Apple client. Do not add UniFFI, a Rust core,
apps/windows, orapps/linuxfor this work. Starting a second native client requires a separate ADR and directory; shared-core extraction waits until that client is committed.
App-Specific Conventions
- Talk to Core only. Bearer OAuth token,
X-Organization-Slugfor org workspaces, omitted for personal. No Prisma, no Postgres, no@sokosumi/databasefrom Swift. - Packages stay UI-free. No
import SwiftUI/AppKitunderPackages/; Mac chrome lives inSokosumi/. - Style: human-review baseline is the Kodeco Swift style guide; machine truth is
.swiftformat+.swiftlint.yml(CI-enforced). See the Swift style rule for the baseline and the deliberate deviations. - UI/UX parity: current web behavior takes precedence over stale requirements; update
PARITY.mdto match and proceed without a scope question for that discrepancy. Preserve explicit native-control preferences and the authorized chat boundary. Before changing chat UI, inspect the corresponding web source and running interaction or supplied recording. Preserve action availability, loading/error states, spacing hierarchy, focus and hover behavior while using native controls. Inspect edge clipping, rounded-corner transitions, text truncation, icon alignment, and padding while scrolling and resizing, including avatars and reactions near the composer. Verify the rendered Apple result; passing builds alone do not prove visual correctness. Record any unverified interaction explicitly. - SwiftUI work: load the app-scoped
swiftui-expert-skill(apps/apple/.agents/skills/) when writing, reviewing, or refactoring SwiftUI. Never install it at the repo root. Hop@Publishedwrites off the current view update (Gotchas).
App-Specific Commands
Xcode 27+ required (swift-tools-version: 6.4). Install pinned lint tooling once from this directory (Mint itself comes from Homebrew; the tool versions come from Mintfile):
brew install mint && mint bootstrap
Run from apps/apple/ (always via mint run so the pinned versions execute):
mint run swiftformat . # format the tree
mint run swiftformat --lint . # check formatting without writing (CI runs this)
mint run swiftlint lint --strict # lint (CI runs this)
xcodebuild -workspace Sokosumi.xcworkspace -scheme Sokosumi -configuration Debug \
-destination 'platform=macOS,arch=arm64' \
-skipPackagePluginValidation DEVELOPMENT_TEAM= CODE_SIGN_IDENTITY=- build
xcodebuild test -workspace Sokosumi.xcworkspace -scheme Sokosumi \
-configuration Debug -destination 'platform=macOS,arch=arm64' \
-skipPackagePluginValidation DEVELOPMENT_TEAM= CODE_SIGN_IDENTITY=- \
-enableCodeCoverage NO # app tests + all five package suites
swift test --package-path Packages/SokosumiChat # fast single-package rerun
The Debug configuration is its own app, "Sokosumi Dev" (com.sokosumi.app.dev), so a build from Xcode never shares the installed app's sandbox container, defaults, keychain session or Launch Services entry. Release keeps com.sokosumi.app. Web's apple-app-site-association lists both IDs, so the Debug build can sign in through the same HTTPS callback.
CI holds no Apple certificates for the gate job, so Xcode test overrides with DEVELOPMENT_TEAM= / CODE_SIGN_IDENTITY=- (ad-hoc). Keep those flags when adding steps to that job. Publish macOS DMG is the exception and must stay one: it archives with DEVELOPMENT_TEAM=$TEAM_ID, CODE_SIGN_STYLE=Manual and CODE_SIGN_IDENTITY="Developer ID Application", because a downloadable build has to be genuinely signed. Do not paste the ad-hoc flags into it.
App-Specific Testing
-
The
Sokosumiworkspace scheme runs all six suites:SokosumiTestsplusCoreAPITests,SokosumiAuthTests,SokosumiChatTests,SokosumiRealtimeTestsandSokosumiWorkspaceTests. Narrow a run with-only-testing:<target>, or useswift test --package-path Packages/<name>for a fast single-package rerun. A new package must be added to bothSokosumi.xcworkspace/contents.xcworkspacedataand the scheme'sTestables, or its tests run nowhere. -
Ask Xcode before building.
XcodeListTargets/XcodeListSchemes(Xcode MCP) orxcodebuild -workspace Sokosumi.xcworkspace -listsay which targets and schemes exist in seconds;GetTestListreturns every test with file paths and costs roughly 15k tokens, so reach for it only to pick a specific test to run. ATestableReferenceXcode does not recognise is dropped silently —xcodebuildexits 0 having run fewer suites — so confirm a new test target inXcodeListTargetsrather than trusting a green run. -
Reuse the shared derived data. A fresh
-derivedDataPathcosts about 3 GB. Use one only where a clean graph is the point (countingdynamic-producttargets, see Gotchas) and delete it afterwards; several left behind fill the disk and later runs die withNo space left on device. -
Coverage stays off (
-enableCodeCoverage NO): ably-cocoa'sAblyDeltaCodecC target cannot link the profile runtime (SOK-976). -
Fake Core HTTP at the OpenAPI
ClientTransportboundary. Do not test SwiftUI layout, Keychain, orASWebAuthenticationSessionas the required suite. -
Tests run in GitHub Actions, from one
xcodebuild build-for-testingof theSokosumiworkspace scheme, which covers the app tests and all five package suites. Do not add per-package jobs — the workspace scheme already runs them, and a package scheme on its own does not build the app host. Threetest-without-buildingprocesses then run at once: the serializedNativeWindowTestsin two shards (the first lists its suites inapple.yml, the second takes the rest of them) and everything else. A test that writes to something every process shares, such asNSPasteboard.generalor a fixed file or defaults name, can collide with another shard; give it a unique name. -
Read a failed
Xcode testfrom its result bundle, not the log. The job printsxcresulttool get test-results summaryand uploads artifactSokosumi-xcresult(kept 3 days). The downloaded folder is the bundle (Info.plist,database.sqlite3; no inner*.xcresult):gh run download <run> -R masumi-network/sokosumi -n Sokosumi-xcresult -D /tmp/xcr && xcrun xcresulttool get test-results tests --path /tmp/xcr. -
The CI runner is not your Mac.
xcode-27is a macOS VM that draws at 1× with real key windows, so window and layout tests (widths, key-window focus) can fail there and pass locally; record CI-measured values, not ones from a Retina display. The runner also has Reduce Motion on and no Vision OCR: an animation ends at once, and a test that waits through an OCR retry loop locally does not wait there, so give every test an explicit wait for the state it checks. Locally, window tests and 1Password SSH commit signing need an unlocked, awake screen: a locked or sleeping display fails the window suites and hangs runs. -
.github/workflows/apple.ymlruns two Apple gates onxcode-27:Xcode test(the workspace scheme) andSwift lint and format(SwiftLint + SwiftFormat via Mint). Both run in parallel and are required status checks onmain, so do not delete or rename either without updating theDefault Branchruleset in the same change. Both are pull_request-only (path-filtered toapps/apple/**); they do not run onpushtomainor onworkflow_dispatch.Warm Apple caches(daily17 3 * * *plusworkflow_dispatch) saves Mint, SwiftPM checkouts, and the Xcode compilation CAS frommainthat PRs restore. Pull requests compile Debug only. -
Publish macOS DMGis the Release compile, and the only job that reads the Developer ID certificate and notary key. Keep those secrets onpublish: it runs after merge or by manual dispatch. A same-repo pull request receives repository secrets, andxcodebuildruns contributor-controlled build scripts and SwiftPM plugins. -
Publish macOS DMGruns on every Applepushtomain(same path filter) and onworkflow_dispatch. It has noneeds:Xcode testis pull_request-only, and a skipped needed job would skip Publish on those main pushes. The merged PR already passed requiredXcode testunder the Default Branch strict checks. Still not gated onSwift lint and format— style cannot make a binary broken, so a working notarized download is not withheld over indentation. It archives with the Developer ID certificate and provisioning profile, exports viaExportOptions.plist, builds a styled disk image withdmgbuild(layout indmg-settings.py), notarizes it withnotarytool --wait, staples the ticket, and publishes the Beta build: it replaces theSokosumi.dmgasset of the single rollingmacos-betaprerelease in place (--clobber; created on first use with--latest=false, its tag moved to the commit, notes naming the commit and saying it is not a Stable release). A Beta build never writesappcast.xmland never touchesmacos-latest, so no installed app is offered it. Both download URLs are permanent:- Stable:
https://github.com/masumi-network/sokosumi/releases/download/macos-latest/Sokosumi.dmg - Beta:
https://github.com/masumi-network/sokosumi/releases/download/macos-beta/Sokosumi.dmg
It is not a required status check — it never runs on a pull request, so it would never report there.
- Stable:
-
To test a change to
Publish macOS DMG, run the workflow manually (Actions → Apple → Run workflow) on your branch.workflow_dispatchstarts Detect Apple changes,Warm Apple caches, andPublish macOS DMG— notXcode testorSwift lint and format. Publish still notarizes, signs the appcast exactly as a Stable tag run does (placeholder notes until the Release PR writes that version's changelog section), uploads the image andappcast.xmlas run artifacts, and touches no release, so it cannot rewrite a link people already have. Every run uploads the artifact, so a specific build is retrievable from the Actions tab; GitHub re-zips artifacts on download, so unzip once to reach the.dmg. -
Signing is mandatory, not decoration. The app is
ENABLE_APP_SANDBOX = YESandENABLE_HARDENED_RUNTIME = YES, so an ad-hoc-signed build that a user downloads is quarantined and Gatekeeper refuses it outright. Notarization is what makes the disk image open on a machine that did not build it. The six secrets this needs are set byscripts/setup-release-signing.sh; do not hand-roll them. One is a Developer ID provisioning profile, which the Associated Domains entitlement requires;scripts/setup-release-signing.sh profilereplaces it alone when it expires. -
One distribution channel: the Developer ID download, built by
Publish macOS DMGin GitHub Actions, signed and notarized. The Xcode Cloud TestFlight track was removed on 2026-10-09; there is no App Store or TestFlight build. -
The Developer ID build updates itself with Sparkle (ADR 0054). Sparkle is a package dependency of the app target only; shared packages never import it.
AppUpdater(inSokosumi/App/) starts the standard updater and backs Check for Updates… only under theSOKOSUMI_UPDATERcompile condition, whichPublish macOS DMGalone sets (SOKOSUMI_UPDATER=YES, turned into an app-target condition by the project), so ad hoc, Debug and local builds never check. The updater code itself compiles in every build. TheSU*keys inSokosumi-Info.plistand Sparkle's mach-lookup exceptions in both entitlements files are baked into every shipped app: changing the feed address or the public key strands installed builds.Publish macOS DMGrunsscripts/check-exported-app.shon the exported app and fails when Sparkle, the feed address, the public key (repository variableSPARKLE_ED_PUBLIC_KEY), the sandbox or the installer exceptions are missing; run it on a local Release build the same way. -
Stable releases come from the Release PR.
macos-release.ymlruns Release Please on every push tomainwith the masumi GitHub App token (release-please-config.json, componentmacos). It keeps one Release PR open that collects the Conventional Commits touchingapps/apple/**, bumpsVersion.xcconfigand writes that version'sCHANGELOG.mdsection. A human merges it; that tagsmacos-v<version>and creates the release, and the tag push starts the publish below. Never cut a release by hand-pushing a tag or editingVersion.xcconfigoutside the Release PR. -
A
macos-v<version>tag push publishes a Stable release fromapple.yml, never another workflow: its run number is the build number, and a new workflow would restart below shipped builds. The run refuses a tag that disagrees withVersion.xcconfig, signs the stapled disk image withSPARKLE_ED_PRIVATE_KEYinto a one-item appcast (Sparkle'sgenerate_appcast, tarball pinned by version and checksum in the job), whose item points at the immutablemacos-v<version>asset and whose notes arescripts/release-notes.sh(that version'sCHANGELOG.mdsection plus the release link).scripts/check-appcast.shthen fails unless the signature verifies againstSPARKLE_ED_PUBLIC_KEY, the length is the disk image's and the URL, versions and notes match;scripts/test-check-appcast.shcovers it inSwift lint and format. Only then does it attach the image tomacos-v<version>, createmacos-lateston first use (--latest=false), replace itsSokosumi.dmgand thenappcast.xmlin place, and keep GitHub's Latest badge on the newestcli-v*release. Beta publishing never runs on a tag.
App-Specific Gotchas
- Do not hand-edit generated files.
CoreAPIderived sources come from Core'sopenapi.jsonvia the OpenAPIGenerator plugin. Regenerate; never patch the output. - Bumping tool versions is deliberate.
Mintfilepinsswiftlint/swiftformatexactly. To upgrade: bump the pin, runmint bootstrap, run both checks, commit the pin together with any tree/config fallout. Never float the pin to chase a single new rule. - Xcode template code ships 4-space indent. Run
swiftformat .on new files from templates. SokosumiTestsdeclares no package products. It is hosted in the app (TEST_HOST/BUNDLE_LOADER), and the app already links every package, so testsimportthose modules and bind to the app's copy. Adding a package product to the test target (Frameworks phase orpackageProductDependencies) makes Xcode rebuild all ~75 package products as dynamic frameworks, and with code coverage on their C sources fail to link (Undefined symbol: ___llvm_profile_runtime). CI passes-enableCodeCoverage NOand would not notice; a local run with coverage on would. Check withxcodebuild … -enableCodeCoverage YES build-for-testingin a fresh derived-data path. A package only the tests need goes on the app target too, or into a package test target.Bundle.moduleis per target. Giving a package's test targetresources:generates aBundle.modulefor that target which shadows the library's through@testable import, so tests reading library resources look in the test bundle and fail.SokosumiChatnames its own bundle explicitly asChatResources.bundleat every call site; do the same in any package whose tests gain resources. Reading a fixture from the source tree instead is caught by thesource_relative_fixturelint rule.- Hop
@Publishedwrites off the current view update.List(selection:)setters,onScrollGeometryChange/ preference callbacks,onAppear, andonChangescheduleTask { @MainActor in … }before callingWorkspaceState/AuthState. Button and Menu actions publish in place. The models stay synchronous so tests call them directly. Lint andxcodebuild testdo not catch this; a debug run's Issue navigator (purple SwiftUI) or/usr/bin/log show --last 5m --info --predicate 'subsystem == "com.apple.runtime-issues" AND process == "Sokosumi"'does. A burst of the same fault in one millisecond is this pattern. - A view update can carry an older
@Statethan an AppKit view already holds. SwiftUI gives each pending transaction the state as it stood before the later ones, soupdateNSViewmay see the value before the last keystroke. Never overwrite a representable's own edits from that value: compare off the update (Task { @MainActor in … }), where the binding reads the live value (Coordinator.adoptOutsideDraft).
Interactive signing
Stop each agent-launched app after its interactive check and before launching another variant. After tests, verify no test-host app remains running; close only instances launched by this task. Do not leave multiple test builds open.
For interactive launches use the configured Apple Development identity and team GVWN7HXYJB, with a separate derived-data directory such as /tmp/sokosumi-interactive-signing. Pass DEVELOPMENT_TEAM=GVWN7HXYJB CODE_SIGN_IDENTITY='Apple Development' to Xcode. Keep ad-hoc builds for CI/tests separate from interactive launches so rebuilding does not repeatedly change the identity used to access the saved Keychain session.
OAuth and Core setup
- Use a first-party public OAuth client with PKCE (no embedded client secret), registered through the existing Core OAuth client machinery by an operator. Scopes are
openid,sokosumi:api, andoffline_access; use the system browser and Keychain-backed token persistence. Do not substitute API keys or browser cookies for the human session. - Each environment's client row sets
signUpOrigintomac. The iPhone app gets its own client row withios; sharing the Mac client records its sign-ups asmac. ADR 0052 has the correction for earlierunknownsign-ups. - The client row's only redirect URI is
https://app.sokosumi.com/auth/apple/callback. Never register a custom scheme on it, and never addCFBundleURLTypesback: the row skips consent (ADR 0046), so a scheme any app can register would hand that app a code silently. The host is fixed for every environment, because the entitlement names it; a local Core redirects to the same production link. Configuration lives inPackages/SokosumiAuth/Sources/SokosumiAuth/OAuthConfiguration.swift. - The HTTPS callback needs three things that agree:
webcredentials:app.sokosumi.cominSokosumi/Sokosumi.entitlements, this app's ID underwebcredentialsin web's/.well-known/apple-app-site-association, and a build signed for the team.ASWebAuthenticationSession.Callback.httpsrequires macOS 14.4 / iOS 17.4, so an iOS target's minimum is 17.4, not 17.0; the packages stay at iOS 17. - Associated Domains is a restricted entitlement: macOS kills an ad-hoc signed app that carries it. A build with no team (
DEVELOPMENT_TEAM=) therefore signs withSokosumi/SokosumiAdHoc.entitlements, selected byCODE_SIGN_ENTITLEMENTSin the project. Such a build runs tests but cannot sign in. Keep the two entitlement files the same apart from that key. - Configure the Core base URL for the intended environment. Local Core uses this checkout's portless HTTPS URL. Personal requests omit
X-Organization-Slug; organization requests carry their slug.GET /v1/users/me/workspacesmust list a workspace before chat is usable; an empty list opens the web setup flow (pending invitations or identity onboarding). Switching sends the workspace id toPUT /v1/users/me/workspaces/preferred(ADR 0051). The app never creates a Workspace: the consent screen used to create the personal one, and without that screen a person with none is told to finish setup on the web. - Every Core request names the build in
X-Sokosumi-Client: macos/<CFBundleVersion>(ClientBuildMiddlewareinCoreAPI, composed inClient.workspacein SokosumiWorkspace; the app passesCoreSettings.build). Core's 426client_update_requiredand 404route_not_foundbecomeCoreUpdateRequired, which opens the Update screen or the update copy linkingmacos-latest/Sokosumi.dmg(ADR 0053). Build 1 is a local build and is never turned away. - Refresh the selected OpenAPI operations from a generated Core specification with
python3 scripts/update-core-api.py /path/to/core-openapi.jsonfromapps/apple. Append an existing Core path such as/chats/rooms/{id}/unreadto include it, or prefix one with-to drop it. The script retains selected operations, resolves transitive schemas, and writes literal UTF-8 because escaped non-BMP examples can break the generator's YAML parser. Never hand-edit the snapshot or generated Swift output. This workflow does not authorize changing Core contracts or web files. - Realtime uses the existing workspace-scoped Ably token endpoint and a persisted per-install
clientInstanceId; follow ADR 0003 and ADR 0014 for identity and event contracts. Behavior and the authorized feature boundary are tracked inPARITY.md; architecture decisions remain indocs/adr/(including0028-apple-native-clients.md).
