mirror of
https://github.com/civitai/civitai.git
synced 2026-09-20 22:08:18 +08:00
6d918e4a96
* feat(app-blocks): full-page apps (W10) on a slot-registry foundation — DARK
Builds the durable W10 full-page App Blocks surface on a clean slot-registry
foundation, fully dark behind two flags so merging changes nothing
user-visible. Combines PR1 (slot registry) + PR2 (entity-aware mint) + PR4
(page route) from the W10 plan; PR3 (install/resolver polymorphism + migration)
is deferred — a page is STATELESS (no install row, no migration).
Foundation (PR1, behavior-preserving):
- New src/shared/constants/slot-registry.ts: SlotDef + SLOT_REGISTRY holding
today's 3 model slots + one new entity=none `app.page` slot. KNOWN_SLOT_IDS
re-exported from the registry under the same name (model-only enum), so the
reuse sites in blocks.router.ts are byte-identical. Marketplace slot filter
and client unions derive from the registry. Regression-locked: KNOWN_SLOT_IDS
.options deep-equals the historical model-slot tuple.
Entity-aware mint + binding (PR2, security-sensitive, inert):
- block-tokens mint generalized to an entity discriminator. MODEL path is
byte-identical (ctx = { modelId, slotId }, no entityType added). PAGE path
(entity=none): resolves a synthetic page_<appBlockId> from the approved
AppBlock, mints a viewer-scoped token with ctx = { slotId, entityType:'none' }
(no modelId — can never satisfy a model-bound check), gated on the new
appBlocksPages flag, page-slot-only, page_ instance-id-only.
- HARD RULE: a page mint rejects ai:write:budgeted / buzz:read:self /
social:tip:self (belt over the manifest + approved-scope intersection).
- BlockSlot remount key generalized to ${slotId}:${entityType}:${entityId}
(slotRemountKey) — preserves the exact model remount-on-nav behavior (H-4).
W10 page route + host (PR4, dark):
- New src/pages/apps/run/[slug]/[[...path]].tsx — SSR gates on
appBlocks && appBlocksPages FIRST (fail-closed notFound), resolves the
approved page app by slug (== block_id), renders a full-bleed PageBlockHost
under the W7 trust chrome with subPath deep-linking (NAVIGATE + ROUTE_CHANGED,
shallow-routed, traversal-guarded).
- New PageBlockHost.tsx — separate from the model IframeHost (kept untouched) so
the model path is not destabilized; reuses usePostMessage / IframeInitController
/ AppBlockChrome / intersectSandbox.
Manifest + discovery:
- Manifest validator: validates page:{path,title,icon?} AND closes the
pre-existing gap by validating targets[].slotId ∈ KNOWN_SLOT_IDS.
- Public manifest projection surfaces a hasPage boolean (no internals); "Open
app" affordance on /apps cards + the detail page, flag-gated.
Flag: new dark appBlocksPages (fliptKey app-blocks-pages-enabled,
availability ['mod']). Both flags off → fully inert.
Tests: slot-registry regression lock; entity-agnostic remount key preserves
model behavior; manifest validator accepts page + rejects unknown slotId; page
mint carries no money scopes, is flag-gated + approved-only + stateless (no
subscription row); model token claims byte-identical for generate-from-model.
Plus a preview e2e spec (tests/preview-apps-page.spec.ts) driving a mod opening
/apps/run/<slug> and asserting the iframe mounts + receives BLOCK_INIT — runs on
the PR preview (needs the flag relaxed there). Full existing AppBlocks corpus
stays green (220 unit tests pass).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: clarify block_id is globally unique (audit nit)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(app-blocks): W10 page-host audit fixes — authoritative trust tier + real granted scopes + terminal fallback (DARK)
Addresses the should-fix audit findings on the W10 full-page-apps substrate
(PR #2606). Feature stays DARK (no flag changes); model render path untouched.
#2 (security): resolvePageBlockBySlug now sources the iframe sandbox's trust
tier from the authoritative, mod-controlled `AppBlock.trustTier` COLUMN, not
the publisher-self-declared `manifest.trustTier` (which reintroduced the C1
trust-tier self-escalation class for the page sandbox). Mirrors the model
render path's column-is-authoritative treatment. Column value wins (e.g.
column='unverified' over manifest='internal' → restrictive sandbox).
#3/#6 (functional): PageBlockHost stops hardcoding `scopes: []` in BLOCK_INIT /
TOKEN_REFRESH / REQUEST_TOKEN. The page route now threads the page manifest's
declared scopes + the mint's missingScopes/needsConsent/error into the host,
which advertises the REAL granted set (declared − missing) the JWT carries
(apps:storage:*). A hard mint error surfaces an `error` terminal state instead
of hanging at no_token.
#4 (UX): terminal states (timeout/fatal/no_token/error) render a BlockFallback
message inside the page frame instead of a blank viewport (full-page surface
collapsing to null is just an empty screen). Anti-spoof posture preserved —
the message is host chrome, not the block.
#5 (partial): page-visibility SUSPEND/RESUME listener with cleanup (mirrors
IframeHost). REQUEST_SIGN_IN deliberately left for the flag-widen.
#7 (nit): sandbox extracted independently of `iframe.src` being a string.
Pure logic extracted to pageBlockHostLogic.ts (grantedPageScopes,
pageFallbackReason) for node-vitest coverage, mirroring the hostRenderDecision
pattern. New tests: block-registry.resolve-page (trust-tier column authority,
sandbox/#7, scope surfacing) + pageBlockHostLogic (granted scopes ≠ [], terminal
→ fallback). Behavior-preserving corpus (oldBehaviorProof, slotRemountKey,
slot-registry, page-mint, block-token, block-manifest-validator,
block-scope.middleware, iframeInitController) all green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Video: https://discord.com/channels/955572167662260295/1062092338698145812/1338591521401733192
# Playwright Testing
### Goal
E2E testing for the main app.
Catch potential issues when changing code, or evaluate edge cases.
### Anti-Goal
For this to be a frustrating pain-in-the-ass time vampire.
It's better to have 1 decent test than try to do 10 perfect ones and give up because it's too time consuming.
### What to Do
- Write tests for common user flows (fill a form, click a button, get a result)
- Handle what-if scenarios (errors, browsers, etc)
- A good rule of thumb is: if it's been reported as a bug, we should have a test in place for it (and things like it)
### What Not to Do
- Write "1==1" tests (dopamine hit, but pointless)
- Mandate coverage percentages (leads to annoyance and features not being done)
---
### How
Testing is intended to work on local development (docker) for consistency with users/data and easy tear down.
1) Run local services (`make init` or devcontainers)
2) Create a file in the `tests/` directory, or use an existing one. Doesn't really matter. Open to directory structure, so something like `tests/generator/gen-queue.spec.ts` would be reasonable.
3) Start writing tests.
(a) can be done by hand if you know what you're looking to do
(b) easier approach: `npm run test:gen -- --load-storage tests/auth/{user}.json --viewport-size 1920,1080 http://localhost:3000/{url}`
- This allows you to create tests by interacting with the page and picking locators
(c) we'll need better locators, especially for icons. add `data-testid=` to the places you need them (they'll be stripped from production)
(d) use the various authed users to test different scenarios (mod, full access, muted, etc)
(e) feel free to mock responses from any of the APIs, but in general it's best to only do this for external services
4) Run with either `npm run test` or `npm run test:ui` to do it interactively with screenshots
5) If you need to reset the db after each test, you can either:
(a) clean up the mutations as part of the test (delete an object you just made)
(b) `make boostrap-db` to reset the whole database back to normal
6) We'll eventually set up the github action to run this before a deploy
### Test Failures
There are 4 types of test failures:
1) A bad test (always fail)
- these might have bad selectors or inaccurate logic
- **solution**: fix them
2) A flaky test (sometimes fail)
- frustrating tests which seem to pass most of the time, but not always
- this is usually a result of race conditions, mismatched timing, or not properly awaiting events like animations
- **solution**: narrow down which part of the test fails, and catch the flaky issue
3) Intended code change
- we might have changed the verbiage on a button, which makes certain locators no longer work
- this is fine, although locators should try to be as agnostic as possible
- alternatively, we might have simply changed the business logic
- **solution**: in either case, simply update the test itself
4) Unintended code change
- you've changed something in the app, and a test breaks due to the introduction of a bug
- this is the major reason we have tests
- **solution**: leave the tests alone, they're doing their job. fix the code.