A calm place to write long-form, and publish it to the open social web. skypress.blog/
0

Configure Feed

Select the types of activity you want to include in your feed.

at trunk 86 lines 5.8 kB View raw View rendered
1# Working agreement for SkyPress 2 3SkyPress is built from a detailed brief (see `docs/` and the original build brief). This 4file captures the durable, non-obvious constraints an agent needs before touching code. 5Read the relevant `docs/decisions/*` and `docs/specs/*` before working in an area. 6 7## Process 8 9- **Brainstorm → spec → plan → build, test-driven.** Write the failing test first. 10- **Record non-obvious calls** in `docs/decisions/NNNN-title.md` (context, options, 11 choice, why). These are first-class deliverables. 12- **Vertical slices over horizontal layers.** Keep the app runnable at every step. 13- **Where specs live.** `docs/specs/sp0``sp12` are the **archive of the initial v1 build** 14 (all Built/Complete) — read them for the "how" and the cross-referenced `Depends on` 15 history, but don't extend the SP numbering. New, post-v1 work gets a dated design + plan 16 pair under `docs/superpowers/specs/` and `docs/superpowers/plans/` 17 (e.g. `2026-06-09-editor-page-rework-design.md`). Durable rationale still graduates to a 18 `docs/decisions/NNNN-*.md`. 19 20## Hard constraints (learned the hard way — see decisions) 21 221. **React 18 only.** `@wordpress/block-editor@15.x` peer-depends on `react@^18`. Do not 23 introduce React 19. (Decision 0001) 242. **SkyPress depends on `@wordpress/block-editor` and friends directly** at the current 25 release line; there is **no `@wordpress/*` `overrides` map** (only `react`/`react-dom` 26 stay pinned). Depending directly, the tree resolves to a single coherent copy of every 27 store singleton — `@wordpress/data`, `core-data`, `element`, `blocks`, `block-editor`28 without any overrides. The old duplicate-registry crash (`reading 'get' of undefined`) 29 came specifically from IBE pinning an old line while transitive caret ranges floated a 30 newer one; that root cause is gone. Upgrading is now a normal `@wordpress` version bump, 31 **not** regenerating an override map. One caveat: `core-data` / `notices` / `date` install 32 as multiple nested copies (no hoisted top-level one) and each registers its store → 33 `Store "core" is already registered` + a split registry. The fix is the bundler dedupe 34 backstop — run `npm dedupe`, then list them in `resolve.dedupe` in **both** 35 `astro.config.mjs` and `vitest.config.ts` (deduping before the hoist *breaks* the build). 36 (Decision 0021 — supersedes the pinning half of Decision 0003.) 373. **Reading pages must never import `@wordpress/*`.** The editor stack is browser-only 38 (touches `window`/`moment`/registries at import) and cannot render server-side. Use 39 the dependency-free `src/lib/blocks/render.ts`. `@wordpress` belongs only in the 40 editor island and in tests. (Decision 0003) 414. **Render fidelity is test-locked.** `render.ts` must match 42 `@wordpress/blocks.serialize()` for the curated blocks — `render.test.ts` enforces it. 43 Adding a block means adding a `render.ts` case **and** a fidelity assertion. 445. **Curated block allowlist is the content model.** Add blocks deliberately; removing 45 one after content exists is a breaking change. (Decision 0002) 466. **Untrusted content:** stored block trees come from arbitrary PDSes. The reader 47 **sanitises** HTML before injecting it (`src/lib/reader/sanitize.ts`). Three standing 48 rules for the read path: (a) any server-side `fetch` to a host derived from user input 49 (a handle, `did:web`, a PDS `serviceEndpoint`) MUST go through `src/lib/net/safe-fetch.ts` 50 (SSRF guard); (b) never inject PDS-sourced HTML without sanitising — turn document 51 blocks into HTML through `src/lib/reader/render-article.ts`, which runs 52 blob-resolve → render → sanitise in the one safe order (Decision 0018); (c) read routes 53 resolve author/publication/document through `src/lib/reader/read-context.ts` — don't 54 re-assemble the handle → DID → PDS → slug-match → site-join chain in page frontmatter. 55 (Decision 0016) 567. **OAuth is a browser public client** (`@atproto/oauth-client-browser`, Decision 0004). 57 In **dev you must serve on `http://127.0.0.1:<port>`, not `localhost`** (atproto 58 loopback requirement), and the loopback `client_id` must be path-less — see 59 `src/lib/auth/oauth.ts`. Auth + editor live in the `Studio` client-only 60 (`client:only="react"`) island — the editor is composed directly from 61 `@wordpress/block-editor`, not by wrapping `IsolatedBlockEditor`. 628. **Colocated tests under `src/pages/` MUST be underscore-prefixed** (e.g. 63 `_index.meta.test.ts`). Astro's file router imports every `.ts` in `src/pages/` during 64 static-path collection; a `*.test.ts` there runs its top-level `import … from 'vitest'`, 65 which throws outside the vitest runner → the build's prerender server 500s 66 (`Vitest failed to access its internal state`). A leading `_` makes Astro ignore the 67 file as a route while vitest's `src/**/*.test.ts` glob still finds it. 689. **`@wordpress/*` is inlined via `ssr.noExternal` in the vitest config, but 69 `moment`/`moment-timezone` stay external** (native CJS). Inlining moment breaks 70 moment-timezone's augmentation of it (`moment.tz` ends up undefined). Separately, 71 `@wordpress/block-editor` ships no types, so its surface is declared in 72 `src/types/wordpress.d.ts`. 73 74## Product guardrails (from the brief) 75 76- SkyPress is an **editor + OAuth client + public renderer** — never a PDS or relay. 77- **OAuth only** (no app passwords). Secrets never in the client. 78- **Don't surprise users**: publishing also creates a Bluesky post — the UI must say so. 79- Lexicon discipline: prefer optional fields + open unions; treat shipped constraints as 80 frozen (additions only, else `-v2`). License: **GPL-2.0-or-later**. 81 82## Commands 83 84```sh 85npm run dev | build | preview | test | check 86```