@bongos/core 1.19.248 → 1.19.250
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +53 -28
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +10 -0
- package/clients/bongos-client/index.cjs +10 -0
- package/clients/bongos-client/index.d.ts +14 -0
- package/clients/bongos-client/index.mjs +10 -0
- package/docs/adr/0178-the-copy-desk-flag-and-queue.md +131 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +257 -3
- package/docs/api-reference.md +10 -1
- package/docs/file-map.md +1 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +10 -0
- package/modules/hall-ui/public/copy-desk.css +119 -0
- package/modules/hall-ui/public/copy-desk.html +139 -0
- package/modules/hall-ui/public/copy-desk.js +352 -0
- package/modules/hall-ui/public/shell.js +9 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/run-unit-tests.js +11 -1
- package/src/bongos/module-scope-map.js +38 -0
- package/src/bongos/serve-internal.js +11 -1
- package/src/module-api.js +5 -1
- package/tests/copy_desk_flags_db.mjs +250 -0
- package/tests/hall_nav.mjs +7 -1
- package/tests/module-scope-map.mjs +40 -0
- package/tests/module_loader.mjs +5 -2
package/docs/api/openapi.json
CHANGED
|
@@ -73,6 +73,10 @@
|
|
|
73
73
|
"name": "claims",
|
|
74
74
|
"description": "Endpoints under `/api/bongos/claims`."
|
|
75
75
|
},
|
|
76
|
+
{
|
|
77
|
+
"name": "copy-desk",
|
|
78
|
+
"description": "Endpoints under `/api/bongos/copy-desk`."
|
|
79
|
+
},
|
|
76
80
|
{
|
|
77
81
|
"name": "cost",
|
|
78
82
|
"description": "Endpoints under `/api/bongos/cost`."
|
|
@@ -3179,6 +3183,180 @@
|
|
|
3179
3183
|
]
|
|
3180
3184
|
}
|
|
3181
3185
|
},
|
|
3186
|
+
"/copy-desk/flags": {
|
|
3187
|
+
"post": {
|
|
3188
|
+
"operationId": "post_copy_desk_flags",
|
|
3189
|
+
"tags": [
|
|
3190
|
+
"copy-desk"
|
|
3191
|
+
],
|
|
3192
|
+
"summary": "POST /copy-desk/flags",
|
|
3193
|
+
"description": "------------------------------------------------------------------------- POST /copy-desk/flags — file a flag. Body: { scope, surface, string_id?, reason, kind? } rank: any signed-in builder (see the header). -------------------------------------------------------------------------\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
|
|
3194
|
+
"x-rank": "any-builder",
|
|
3195
|
+
"x-source": "modules/copy-desk/routes/copy-desk.js",
|
|
3196
|
+
"requestBody": {
|
|
3197
|
+
"required": true,
|
|
3198
|
+
"content": {
|
|
3199
|
+
"application/json": {
|
|
3200
|
+
"schema": {
|
|
3201
|
+
"$ref": "#/components/schemas/PostCopyDeskFlagsRequest"
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
},
|
|
3205
|
+
"x-validated": true
|
|
3206
|
+
},
|
|
3207
|
+
"responses": {
|
|
3208
|
+
"200": {
|
|
3209
|
+
"description": "Success.",
|
|
3210
|
+
"content": {
|
|
3211
|
+
"application/json": {
|
|
3212
|
+
"schema": {
|
|
3213
|
+
"$ref": "#/components/schemas/PostCopyDeskFlagsResponse"
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
},
|
|
3218
|
+
"400": {
|
|
3219
|
+
"$ref": "#/components/responses/ValidationFailed"
|
|
3220
|
+
},
|
|
3221
|
+
"401": {
|
|
3222
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
3223
|
+
},
|
|
3224
|
+
"403": {
|
|
3225
|
+
"$ref": "#/components/responses/Forbidden"
|
|
3226
|
+
}
|
|
3227
|
+
},
|
|
3228
|
+
"security": [
|
|
3229
|
+
{
|
|
3230
|
+
"builderSession": []
|
|
3231
|
+
}
|
|
3232
|
+
]
|
|
3233
|
+
}
|
|
3234
|
+
},
|
|
3235
|
+
"/copy-desk/flags/{id}/close": {
|
|
3236
|
+
"post": {
|
|
3237
|
+
"operationId": "post_copy_desk_flags_id_close",
|
|
3238
|
+
"tags": [
|
|
3239
|
+
"copy-desk"
|
|
3240
|
+
],
|
|
3241
|
+
"summary": "POST /copy-desk/flags/:id/close",
|
|
3242
|
+
"description": "------------------------------------------------------------------------- POST /copy-desk/flags/:id/close — resolve or dismiss. Body: { status: 'resolved'|'dismissed', resolution_note, resolved_task_id? } rank: metic+archon, OR the flag's author (see the header). -------------------------------------------------------------------------\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
|
|
3243
|
+
"x-rank": "any-builder",
|
|
3244
|
+
"x-source": "modules/copy-desk/routes/copy-desk.js",
|
|
3245
|
+
"parameters": [
|
|
3246
|
+
{
|
|
3247
|
+
"name": "id",
|
|
3248
|
+
"in": "path",
|
|
3249
|
+
"required": true,
|
|
3250
|
+
"schema": {
|
|
3251
|
+
"type": "string"
|
|
3252
|
+
},
|
|
3253
|
+
"description": "Path parameter `id`."
|
|
3254
|
+
}
|
|
3255
|
+
],
|
|
3256
|
+
"requestBody": {
|
|
3257
|
+
"required": true,
|
|
3258
|
+
"content": {
|
|
3259
|
+
"application/json": {
|
|
3260
|
+
"schema": {
|
|
3261
|
+
"$ref": "#/components/schemas/PostCopyDeskFlagsIdCloseRequest"
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
},
|
|
3265
|
+
"x-validated": true
|
|
3266
|
+
},
|
|
3267
|
+
"responses": {
|
|
3268
|
+
"200": {
|
|
3269
|
+
"description": "Success.",
|
|
3270
|
+
"content": {
|
|
3271
|
+
"application/json": {
|
|
3272
|
+
"schema": {
|
|
3273
|
+
"$ref": "#/components/schemas/PostCopyDeskFlagsIdCloseResponse"
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
},
|
|
3278
|
+
"400": {
|
|
3279
|
+
"$ref": "#/components/responses/ValidationFailed"
|
|
3280
|
+
},
|
|
3281
|
+
"401": {
|
|
3282
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
3283
|
+
},
|
|
3284
|
+
"403": {
|
|
3285
|
+
"$ref": "#/components/responses/Forbidden"
|
|
3286
|
+
},
|
|
3287
|
+
"404": {
|
|
3288
|
+
"$ref": "#/components/responses/NotFound"
|
|
3289
|
+
}
|
|
3290
|
+
},
|
|
3291
|
+
"security": [
|
|
3292
|
+
{
|
|
3293
|
+
"builderSession": []
|
|
3294
|
+
}
|
|
3295
|
+
]
|
|
3296
|
+
}
|
|
3297
|
+
},
|
|
3298
|
+
"/copy-desk/queue": {
|
|
3299
|
+
"get": {
|
|
3300
|
+
"operationId": "get_copy_desk_queue",
|
|
3301
|
+
"tags": [
|
|
3302
|
+
"copy-desk"
|
|
3303
|
+
],
|
|
3304
|
+
"summary": "GET /copy-desk/queue",
|
|
3305
|
+
"description": "------------------------------------------------------------------------- GET /copy-desk/queue — the artist's working list. ?status=open (default) | resolved | dismissed | all ?surface=<registry surface id> rank: any signed-in builder. The queue is a work list, not a privilege — and an artist is a craft, not a rank (builders.preferred_disciplines), so gating this by rank would hide the artist's own queue from the artist. -------------------------------------------------------------------------\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
|
|
3306
|
+
"x-rank": "any-builder",
|
|
3307
|
+
"x-source": "modules/copy-desk/routes/copy-desk.js",
|
|
3308
|
+
"responses": {
|
|
3309
|
+
"200": {
|
|
3310
|
+
"description": "Success."
|
|
3311
|
+
},
|
|
3312
|
+
"400": {
|
|
3313
|
+
"$ref": "#/components/responses/BadRequest"
|
|
3314
|
+
},
|
|
3315
|
+
"401": {
|
|
3316
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
3317
|
+
},
|
|
3318
|
+
"403": {
|
|
3319
|
+
"$ref": "#/components/responses/Forbidden"
|
|
3320
|
+
}
|
|
3321
|
+
},
|
|
3322
|
+
"security": [
|
|
3323
|
+
{
|
|
3324
|
+
"builderSession": []
|
|
3325
|
+
}
|
|
3326
|
+
]
|
|
3327
|
+
}
|
|
3328
|
+
},
|
|
3329
|
+
"/copy-desk/strings": {
|
|
3330
|
+
"get": {
|
|
3331
|
+
"operationId": "get_copy_desk_strings",
|
|
3332
|
+
"tags": [
|
|
3333
|
+
"copy-desk"
|
|
3334
|
+
],
|
|
3335
|
+
"summary": "GET /copy-desk/strings",
|
|
3336
|
+
"description": "------------------------------------------------------------------------- GET /copy-desk/strings — search the inventory, to find something to flag. ?q= substring over text or file path · ?surface= · ?limit= (max 200) Read-only over the committed registry — no DB, no tree walk. rank: any signed-in builder, same reasoning as the queue. -------------------------------------------------------------------------\n\n**Rank:** `any-builder` — Any authenticated builder (row-level ownership enforced in-handler).",
|
|
3337
|
+
"x-rank": "any-builder",
|
|
3338
|
+
"x-source": "modules/copy-desk/routes/copy-desk.js",
|
|
3339
|
+
"responses": {
|
|
3340
|
+
"200": {
|
|
3341
|
+
"description": "Success."
|
|
3342
|
+
},
|
|
3343
|
+
"400": {
|
|
3344
|
+
"$ref": "#/components/responses/BadRequest"
|
|
3345
|
+
},
|
|
3346
|
+
"401": {
|
|
3347
|
+
"$ref": "#/components/responses/Unauthorized"
|
|
3348
|
+
},
|
|
3349
|
+
"403": {
|
|
3350
|
+
"$ref": "#/components/responses/Forbidden"
|
|
3351
|
+
}
|
|
3352
|
+
},
|
|
3353
|
+
"security": [
|
|
3354
|
+
{
|
|
3355
|
+
"builderSession": []
|
|
3356
|
+
}
|
|
3357
|
+
]
|
|
3358
|
+
}
|
|
3359
|
+
},
|
|
3182
3360
|
"/cost": {
|
|
3183
3361
|
"post": {
|
|
3184
3362
|
"operationId": "post_cost",
|
|
@@ -15494,6 +15672,82 @@
|
|
|
15494
15672
|
"reward"
|
|
15495
15673
|
]
|
|
15496
15674
|
},
|
|
15675
|
+
"PostCopyDeskFlagsIdCloseRequest": {
|
|
15676
|
+
"type": "object",
|
|
15677
|
+
"properties": {
|
|
15678
|
+
"status": {
|
|
15679
|
+
"type": "string"
|
|
15680
|
+
},
|
|
15681
|
+
"resolution_note": {
|
|
15682
|
+
"type": "string",
|
|
15683
|
+
"minLength": 1
|
|
15684
|
+
},
|
|
15685
|
+
"resolved_task_id": {
|
|
15686
|
+
"type": "integer",
|
|
15687
|
+
"minimum": 1
|
|
15688
|
+
}
|
|
15689
|
+
},
|
|
15690
|
+
"required": [
|
|
15691
|
+
"status",
|
|
15692
|
+
"resolution_note"
|
|
15693
|
+
],
|
|
15694
|
+
"additionalProperties": false
|
|
15695
|
+
},
|
|
15696
|
+
"PostCopyDeskFlagsIdCloseResponse": {
|
|
15697
|
+
"type": "object",
|
|
15698
|
+
"properties": {
|
|
15699
|
+
"ok": {
|
|
15700
|
+
"type": "boolean"
|
|
15701
|
+
},
|
|
15702
|
+
"flag": {}
|
|
15703
|
+
},
|
|
15704
|
+
"required": [
|
|
15705
|
+
"ok",
|
|
15706
|
+
"flag"
|
|
15707
|
+
]
|
|
15708
|
+
},
|
|
15709
|
+
"PostCopyDeskFlagsRequest": {
|
|
15710
|
+
"type": "object",
|
|
15711
|
+
"properties": {
|
|
15712
|
+
"scope": {
|
|
15713
|
+
"type": "string"
|
|
15714
|
+
},
|
|
15715
|
+
"surface": {
|
|
15716
|
+
"type": "string",
|
|
15717
|
+
"minLength": 1,
|
|
15718
|
+
"maxLength": 128
|
|
15719
|
+
},
|
|
15720
|
+
"string_id": {
|
|
15721
|
+
"type": "string",
|
|
15722
|
+
"maxLength": 64
|
|
15723
|
+
},
|
|
15724
|
+
"reason": {
|
|
15725
|
+
"type": "string",
|
|
15726
|
+
"minLength": 1
|
|
15727
|
+
},
|
|
15728
|
+
"kind": {
|
|
15729
|
+
"type": "string"
|
|
15730
|
+
}
|
|
15731
|
+
},
|
|
15732
|
+
"required": [
|
|
15733
|
+
"surface",
|
|
15734
|
+
"reason"
|
|
15735
|
+
],
|
|
15736
|
+
"additionalProperties": false
|
|
15737
|
+
},
|
|
15738
|
+
"PostCopyDeskFlagsResponse": {
|
|
15739
|
+
"type": "object",
|
|
15740
|
+
"properties": {
|
|
15741
|
+
"ok": {
|
|
15742
|
+
"type": "boolean"
|
|
15743
|
+
},
|
|
15744
|
+
"flag": {}
|
|
15745
|
+
},
|
|
15746
|
+
"required": [
|
|
15747
|
+
"ok",
|
|
15748
|
+
"flag"
|
|
15749
|
+
]
|
|
15750
|
+
},
|
|
15497
15751
|
"PostCostRequest": {
|
|
15498
15752
|
"type": "object",
|
|
15499
15753
|
"properties": {
|
|
@@ -18039,9 +18293,9 @@
|
|
|
18039
18293
|
"description": "A required dependency/feature is not configured or is temporarily down."
|
|
18040
18294
|
}
|
|
18041
18295
|
},
|
|
18042
|
-
"x-endpoint-count":
|
|
18043
|
-
"x-schema-count":
|
|
18296
|
+
"x-endpoint-count": 289,
|
|
18297
|
+
"x-schema-count": 325,
|
|
18044
18298
|
"x-undocumented-bodies": 8,
|
|
18045
|
-
"x-response-schemas":
|
|
18299
|
+
"x-response-schemas": 217,
|
|
18046
18300
|
"x-generated-by": "scripts/gds/gen-api-docs.js"
|
|
18047
18301
|
}
|
package/docs/api-reference.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Bongos API reference
|
|
4
4
|
|
|
5
|
-
> **Generated from the live route files** — the route file is authoritative.
|
|
5
|
+
> **Generated from the live route files** — the route file is authoritative. 289 endpoints across 47 route files.
|
|
6
6
|
> Machine-readable spec: [`docs/api/openapi.json`](api/openapi.json) (OpenAPI 3.1). Rendered docs site: **`/docs`** (e.g. `cloudbongos.com/docs`).
|
|
7
7
|
|
|
8
8
|
Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>.md)): `public` < `any-builder` < `metic+archon` < `archon`.
|
|
@@ -143,6 +143,15 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
|
|
|
143
143
|
| POST | `/api/bongos/claims/batch/release` | `any-builder` | `claim_ids`, `force` | Bulk release (task 1758) — the batch twin of resolve(outcome='abandoned'). |
|
|
144
144
|
| POST | `/api/bongos/claims/batch/validate` | `any-builder` | `task_ids` | Dry-run validate (task 1758) — read-only twin of claimTasksBatch's pre-flight checks. |
|
|
145
145
|
|
|
146
|
+
## `copy-desk` (4)
|
|
147
|
+
|
|
148
|
+
| Method | Path | Rank | Body | Description |
|
|
149
|
+
|---|---|---|---|---|
|
|
150
|
+
| POST | `/api/bongos/copy-desk/flags` | `any-builder` | `scope`, `surface`, `string_id`, `reason`, `kind` | ------------------------------------------------------------------------- POST /copy-desk/flags — file a flag. |
|
|
151
|
+
| POST | `/api/bongos/copy-desk/flags/:id/close` | `any-builder` | `status`, `resolution_note`, `resolved_task_id` | ------------------------------------------------------------------------- POST /copy-desk/flags/:id/close — resolve or dismiss. |
|
|
152
|
+
| GET | `/api/bongos/copy-desk/queue` | `any-builder` | — | ------------------------------------------------------------------------- GET /copy-desk/queue — the artist's working list. |
|
|
153
|
+
| GET | `/api/bongos/copy-desk/strings` | `any-builder` | — | ------------------------------------------------------------------------- GET /copy-desk/strings — search the inventory, to find somethin… |
|
|
154
|
+
|
|
146
155
|
## `cost` (1)
|
|
147
156
|
|
|
148
157
|
| Method | Path | Rank | Body | Description |
|
package/docs/file-map.md
CHANGED
|
@@ -124,6 +124,7 @@
|
|
|
124
124
|
│ ├── status-ui/ ← CORE-DOMAIN WEB-SURFACE carve (BV1.R84, default-on): the public status dashboard at status.<apex>, moved from public-status/. NO routes/db/port — declares contributes.webSurfaces:[{host:"status.",dir:"public"}], served by the loader web-surface seam (moduleWebSurfaces → serve-internal). CLAUDE.md, module.json, public/{index.html,status.js,style.css,cursors/,snapshots/}.
|
|
125
125
|
│ ├── hall-ui/ ← CORE-DOMAIN WEB-SURFACE carve (BV1.R85, default-on): the authenticated builders' hall at builders.<apex>, moved from public-builders/ (~13k LOC, the largest). NO routes/db/port — declares contributes.webSurfaces:[{host:"builders.",dir:"public"}] for discovery, but UNLIKE status-ui keeps its DEDICATED serving block in serve-internal (the /builders shim + canonical redirects + citizen/Archon page gates + asset-stamping + hall-widget injection), partitioned out of the generic web-surface loop and served from BUILDERS_DIR. CLAUDE.md, module.json, public/{index.html,builders.js,style.css,work,watch,harbor,gate,pair,settings,ranks,sessions,primer,diagrams,atlas,...}.{html,js},cursors/,chime.wav.
|
|
126
126
|
│ ├── lifecycle/ ← CORE-DOMAIN carve (BV1.R86, default-on): the build state machine — tasks · claims · versions · done-when · goals · dependencies · claim→ship→grade→credit · github-push + merge-lock + conflict-resolve + publish-reconciler. The LAST, most-coupled carve; registers the `lifecycle` kernel port (createTask, classifyTaskKind, tallyPeerVotes, work-tracking reads). CLAUDE.md, module.json, routes/{tasks,claims,versions,done-when,goals,dependencies,gate-approvals,analytics}.js, lifecycle.js, github-push.js, merge-lock.js, conflict-resolve.js, publish-reconciler.js.
|
|
127
|
+
│ ├── copy-desk/ ← CORE-DOMAIN module (task 1003113 / R02 of goal 1000074, default-on): the COPY half of the design contract — ADR 0081 gave design TOKENS a repo-owned source of truth, text had none. Owns the FLAG (anyone in any role marks a string or a whole surface as needing an artist's attention, with a reason required in BOTH the route and a CHECK constraint) and the QUEUE an artist reads it from. READS R01's committed registry (docs/copy-registry.json, generated by scripts/gds/copy-inventory.js) and never writes it. NO LIVE CMS is the hard non-goal: no column here could hold replacement text, and copy_no_cms.mjs pins that. Key is `copy-desk`, not `copy` — the branding contract already owns a `copy` block, so the bare word reds the "core carries no module code" fitness check. CLAUDE.md, module.json, registry.js, flags.js, routes/copy-desk.js, migrations/copy_desk_001_flags.sql, tests/{copy_flags,copy_queue,copy_no_cms}.mjs. Its hall surface lives with hall-ui (public/copy-desk.{html,js,css}), per the one-key-per-web-surface precedent.
|
|
127
128
|
│ ├── character-anim/ ← consistent-character generative animation (ADR 0088) — graded painterly frame sets + manifest, the otb-character-review skill. CLAUDE.md, module.json.
|
|
128
129
|
│ ├── provisioning/ ← stands up + operates a Cloud Bongos instance's own infra (server/DB/domain/DNS/TLS) via the API (ADR 0111); web-tier request+state only, no cloud tokens — a separate control-plane runner drains the intents. Distinct from the `hosting` module (ADR 0092), which brokers compute workloads. CLAUDE.md, module.json.
|
|
129
130
|
│ └── demo/ ← BV1.R52 acceptance-proof module: one rank-gated write route + one namespaced migration proving the loader mounts a scaffolded module with zero core edits. CLAUDE.md, module.json.
|
|
@@ -251,6 +251,16 @@ function createClient(opts = {}) {
|
|
|
251
251
|
// POST /claims/batch/validate — rank: any-builder — POST /claims/batch/validate
|
|
252
252
|
postClaimsBatchValidate: (args) => request("POST", "/claims/batch/validate", { hasBody: true }, args),
|
|
253
253
|
},
|
|
254
|
+
"copyDesk": {
|
|
255
|
+
// POST /copy-desk/flags — rank: any-builder — POST /copy-desk/flags
|
|
256
|
+
postCopyDeskFlags: (args) => request("POST", "/copy-desk/flags", { hasBody: true }, args),
|
|
257
|
+
// POST /copy-desk/flags/{id}/close — rank: any-builder — POST /copy-desk/flags/:id/close
|
|
258
|
+
postCopyDeskFlagsIdClose: (args) => request("POST", "/copy-desk/flags/{id}/close", { hasBody: true }, args),
|
|
259
|
+
// GET /copy-desk/queue — rank: any-builder — GET /copy-desk/queue
|
|
260
|
+
getCopyDeskQueue: (args) => request("GET", "/copy-desk/queue", { hasBody: false }, args),
|
|
261
|
+
// GET /copy-desk/strings — rank: any-builder — GET /copy-desk/strings
|
|
262
|
+
getCopyDeskStrings: (args) => request("GET", "/copy-desk/strings", { hasBody: false }, args),
|
|
263
|
+
},
|
|
254
264
|
"cost": {
|
|
255
265
|
// POST /cost — rank: any-builder — POST /cost
|
|
256
266
|
postCost: (args) => request("POST", "/cost", { hasBody: true }, args),
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* modules/hall-ui/public/copy-desk.css — the Copy desk tab (task 1003113 / R02
|
|
2
|
+
of goal 1000074).
|
|
3
|
+
|
|
4
|
+
Built on the hall's existing tokens (style.css) rather than a new palette: the
|
|
5
|
+
page whose subject is "does this project speak with one voice" would be a poor
|
|
6
|
+
place to introduce a second visual one. Only the shapes this page needs are
|
|
7
|
+
defined here. */
|
|
8
|
+
|
|
9
|
+
.cd-stats {
|
|
10
|
+
display: grid;
|
|
11
|
+
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
|
|
12
|
+
gap: 0.75rem;
|
|
13
|
+
margin: 0.5rem 0 1rem;
|
|
14
|
+
}
|
|
15
|
+
.cd-stat {
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
gap: 0.15rem;
|
|
19
|
+
padding: 0.75rem 0.9rem;
|
|
20
|
+
border: 1px solid var(--line, rgba(255, 255, 255, 0.12));
|
|
21
|
+
border-radius: 0.5rem;
|
|
22
|
+
background: var(--panel, rgba(255, 255, 255, 0.03));
|
|
23
|
+
}
|
|
24
|
+
.cd-stat__n { font-size: 1.6rem; font-weight: 700; line-height: 1.1; }
|
|
25
|
+
.cd-stat__l { font-size: 0.8rem; opacity: 0.7; }
|
|
26
|
+
|
|
27
|
+
.cd-note { font-size: 0.85rem; opacity: 0.75; line-height: 1.5; }
|
|
28
|
+
.cd-note code {
|
|
29
|
+
padding: 0.05rem 0.3rem;
|
|
30
|
+
border-radius: 0.25rem;
|
|
31
|
+
background: var(--panel, rgba(255, 255, 255, 0.06));
|
|
32
|
+
font-size: 0.95em;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.cd-filters { display: flex; flex-wrap: wrap; gap: 0.5rem 0.75rem; align-items: center; margin: 0.5rem 0 1rem; }
|
|
36
|
+
.cd-filters__label { font-size: 0.8rem; opacity: 0.7; }
|
|
37
|
+
.cd-filters__select,
|
|
38
|
+
.cd-browse__input,
|
|
39
|
+
.cd-dialog__select,
|
|
40
|
+
.cd-dialog__input,
|
|
41
|
+
.cd-dialog__text {
|
|
42
|
+
padding: 0.4rem 0.6rem;
|
|
43
|
+
border: 1px solid var(--line, rgba(255, 255, 255, 0.15));
|
|
44
|
+
border-radius: 0.35rem;
|
|
45
|
+
background: var(--panel, rgba(0, 0, 0, 0.2));
|
|
46
|
+
color: inherit;
|
|
47
|
+
font: inherit;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.cd-browse { margin: 0.5rem 0 1rem; }
|
|
51
|
+
.cd-browse__label { display: block; font-size: 0.8rem; opacity: 0.7; margin-bottom: 0.3rem; }
|
|
52
|
+
.cd-browse__input { width: 100%; max-width: 34rem; }
|
|
53
|
+
|
|
54
|
+
.cd-list { display: flex; flex-direction: column; gap: 0.6rem; }
|
|
55
|
+
.cd-empty { padding: 1rem; opacity: 0.65; font-size: 0.9rem; }
|
|
56
|
+
|
|
57
|
+
.cd-card {
|
|
58
|
+
padding: 0.85rem 1rem;
|
|
59
|
+
border: 1px solid var(--line, rgba(255, 255, 255, 0.12));
|
|
60
|
+
border-radius: 0.5rem;
|
|
61
|
+
background: var(--panel, rgba(255, 255, 255, 0.03));
|
|
62
|
+
}
|
|
63
|
+
.cd-card--slim { padding: 0.6rem 0.8rem; }
|
|
64
|
+
.cd-card__head { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; margin-bottom: 0.4rem; }
|
|
65
|
+
.cd-card__target { margin: 0.2rem 0 0.45rem; line-height: 1.5; }
|
|
66
|
+
.cd-card__actions { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; justify-content: flex-end; margin-top: 0.5rem; }
|
|
67
|
+
|
|
68
|
+
/* The quoted string is the product's own words, shown as evidence — so it is
|
|
69
|
+
visually set apart from this page's own voice. Wrapping is preserved because
|
|
70
|
+
leading and trailing space in a string is often exactly what is wrong. */
|
|
71
|
+
.cd-quote {
|
|
72
|
+
quotes: '"' '"';
|
|
73
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
74
|
+
font-size: 0.9em;
|
|
75
|
+
white-space: pre-wrap;
|
|
76
|
+
word-break: break-word;
|
|
77
|
+
}
|
|
78
|
+
.cd-where { font-size: 0.8rem; opacity: 0.6; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
79
|
+
.cd-byline { font-size: 0.8rem; opacity: 0.65; margin-left: auto; }
|
|
80
|
+
.cd-reason { margin: 0.3rem 0 0; line-height: 1.55; }
|
|
81
|
+
.cd-snapshot-label { font-size: 0.8rem; opacity: 0.6; }
|
|
82
|
+
.cd-closed { margin: 0.5rem 0 0; font-size: 0.85rem; opacity: 0.8; line-height: 1.5; }
|
|
83
|
+
.cd-places { margin: 0.3rem 0 0; padding-left: 1.1rem; font-size: 0.8rem; opacity: 0.65; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
84
|
+
|
|
85
|
+
.cd-tag {
|
|
86
|
+
display: inline-block;
|
|
87
|
+
padding: 0.1rem 0.45rem;
|
|
88
|
+
border: 1px solid var(--line, rgba(255, 255, 255, 0.18));
|
|
89
|
+
border-radius: 999px;
|
|
90
|
+
font-size: 0.72rem;
|
|
91
|
+
letter-spacing: 0.02em;
|
|
92
|
+
opacity: 0.85;
|
|
93
|
+
}
|
|
94
|
+
.cd-tag--kind { font-weight: 600; opacity: 1; }
|
|
95
|
+
/* 'untouched' is the only state that is WORK, so it is the only one that carries
|
|
96
|
+
colour — a queue where everything is highlighted highlights nothing. */
|
|
97
|
+
.cd-tag--untouched { border-color: currentColor; opacity: 1; }
|
|
98
|
+
.cd-tag--flagged,
|
|
99
|
+
.cd-tag--settled,
|
|
100
|
+
.cd-tag--moved { opacity: 0.6; }
|
|
101
|
+
.cd-tag--gone { opacity: 0.75; font-style: italic; }
|
|
102
|
+
|
|
103
|
+
.cd-dialog {
|
|
104
|
+
width: min(34rem, 92vw);
|
|
105
|
+
padding: 1.25rem;
|
|
106
|
+
border: 1px solid var(--line, rgba(255, 255, 255, 0.18));
|
|
107
|
+
border-radius: 0.6rem;
|
|
108
|
+
background: var(--bg, #12131a);
|
|
109
|
+
color: inherit;
|
|
110
|
+
}
|
|
111
|
+
.cd-dialog::backdrop { background: rgba(0, 0, 0, 0.55); }
|
|
112
|
+
.cd-dialog__h { margin: 0 0 0.5rem; font-size: 1.1rem; }
|
|
113
|
+
.cd-dialog__target { margin: 0 0 0.9rem; line-height: 1.5; }
|
|
114
|
+
.cd-dialog__label { display: block; margin: 0.75rem 0 0.25rem; font-size: 0.85rem; font-weight: 600; }
|
|
115
|
+
.cd-dialog__hint { display: block; font-weight: 400; font-size: 0.78rem; opacity: 0.65; line-height: 1.45; margin-top: 0.1rem; }
|
|
116
|
+
.cd-dialog__select, .cd-dialog__input, .cd-dialog__text { width: 100%; }
|
|
117
|
+
.cd-dialog__text { resize: vertical; line-height: 1.5; }
|
|
118
|
+
.cd-dialog__err { min-height: 1.2rem; margin: 0.5rem 0 0; font-size: 0.85rem; color: var(--danger, #ff8080); line-height: 1.45; }
|
|
119
|
+
.cd-dialog__actions { display: flex; gap: 0.5rem; justify-content: flex-end; margin-top: 0.9rem; }
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Copy desk — {{worldName}}</title>
|
|
7
|
+
<meta name="description" content="The artist's queue over the words this project shows people: flagged copy, who flagged it and why, and the duplicated strings nobody has looked at yet.">
|
|
8
|
+
<link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
12
|
+
<link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
|
|
13
|
+
<link rel="stylesheet" href="/builders/copy-desk.css">
|
|
14
|
+
</head>
|
|
15
|
+
<body data-page="copy-desk" data-page-title="Copy desk">
|
|
16
|
+
|
|
17
|
+
<div class="app">
|
|
18
|
+
<aside class="app-sidebar" id="app-sidebar" aria-label="Primary"></aside>
|
|
19
|
+
<div class="app-scrim" id="app-scrim" hidden></div>
|
|
20
|
+
<div class="app-body">
|
|
21
|
+
<header class="app-topbar" id="app-topbar"></header>
|
|
22
|
+
<div class="app-content">
|
|
23
|
+
<div class="page-head"><p class="page-head__sub" id="cd-sub">Loading the copy desk…</p></div>
|
|
24
|
+
|
|
25
|
+
<main>
|
|
26
|
+
<!-- The counts. Deliberately four numbers and not a dashboard: open work,
|
|
27
|
+
the fast pile (targets that have since been edited away), untouched
|
|
28
|
+
duplicates, and how big the inventory is. -->
|
|
29
|
+
<section class="scroll" id="cd-summary-scroll" aria-labelledby="cd-summary-h">
|
|
30
|
+
<h2 id="cd-summary-h" class="scroll__h">Where the words stand</h2>
|
|
31
|
+
<div class="cd-stats" id="cd-stats"></div>
|
|
32
|
+
<p class="cd-note" id="cd-registry-note"></p>
|
|
33
|
+
</section>
|
|
34
|
+
|
|
35
|
+
<!-- The queue. -->
|
|
36
|
+
<section class="scroll" id="cd-flags-scroll" aria-labelledby="cd-flags-h">
|
|
37
|
+
<h2 id="cd-flags-h" class="scroll__h">Flagged</h2>
|
|
38
|
+
<p class="scroll__lede">Copy someone has judged as needing attention, with their reason. Anyone can file one; closing it needs Metic+, or that it is your own.</p>
|
|
39
|
+
<div class="cd-filters">
|
|
40
|
+
<label class="cd-filters__label" for="cd-status">Show</label>
|
|
41
|
+
<select id="cd-status" class="cd-filters__select">
|
|
42
|
+
<option value="open" selected>Open</option>
|
|
43
|
+
<option value="resolved">Resolved</option>
|
|
44
|
+
<option value="dismissed">Dismissed</option>
|
|
45
|
+
<option value="all">Everything</option>
|
|
46
|
+
</select>
|
|
47
|
+
<label class="cd-filters__label" for="cd-surface">Surface</label>
|
|
48
|
+
<select id="cd-surface" class="cd-filters__select"><option value="">All surfaces</option></select>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="cd-list" id="cd-flags"><div class="cd-empty">Loading…</div></div>
|
|
51
|
+
</section>
|
|
52
|
+
|
|
53
|
+
<!-- The one signal a machine can compute with no judgement layer at all
|
|
54
|
+
(R01's registry computes it; this only subtracts what has been dealt
|
|
55
|
+
with). Stale and off-voice need a person or a judge — this does not. -->
|
|
56
|
+
<section class="scroll" id="cd-dupes-scroll" aria-labelledby="cd-dupes-h">
|
|
57
|
+
<h2 id="cd-dupes-h" class="scroll__h">Said more than once</h2>
|
|
58
|
+
<p class="scroll__lede">The same sentence, in several places. Sometimes that is right and the answer is to dismiss it; sometimes it is seven copies of an error message that should have been one. Nobody has looked at the ones marked untouched.</p>
|
|
59
|
+
<div class="cd-list" id="cd-dupes"><div class="cd-empty">Loading…</div></div>
|
|
60
|
+
</section>
|
|
61
|
+
|
|
62
|
+
<!-- Find any string in the product and flag it. Without this the queue
|
|
63
|
+
could only ever contain what a machine had already noticed. -->
|
|
64
|
+
<section class="scroll" id="cd-find-scroll" aria-labelledby="cd-find-h">
|
|
65
|
+
<h2 id="cd-find-h" class="scroll__h">Find something to flag</h2>
|
|
66
|
+
<p class="scroll__lede">Search every string the product shows a person, by what it says or the file it lives in.</p>
|
|
67
|
+
<div class="cd-browse">
|
|
68
|
+
<label class="cd-browse__label" for="cd-search">Search the copy</label>
|
|
69
|
+
<input type="search" id="cd-search" class="cd-browse__input" autocomplete="off"
|
|
70
|
+
placeholder="a phrase you saw, or a file name…">
|
|
71
|
+
<p class="cd-note" id="cd-search-count" role="status" aria-live="polite"></p>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="cd-list" id="cd-results"></div>
|
|
74
|
+
</section>
|
|
75
|
+
</main>
|
|
76
|
+
|
|
77
|
+
<footer class="app-foot" id="app-foot"></footer>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<!-- The flag composer. A dialog rather than an inline field because the reason is
|
|
83
|
+
the point: a one-line input invites "typo", and the queue is only worth
|
|
84
|
+
reading if the reasons are. -->
|
|
85
|
+
<dialog id="cd-dialog" class="cd-dialog">
|
|
86
|
+
<form method="dialog" id="cd-form">
|
|
87
|
+
<h3 class="cd-dialog__h" id="cd-dialog-h">Flag this copy</h3>
|
|
88
|
+
<p class="cd-dialog__target" id="cd-dialog-target"></p>
|
|
89
|
+
<label class="cd-dialog__label" for="cd-kind">What kind of wrong?</label>
|
|
90
|
+
<select id="cd-kind" class="cd-dialog__select">
|
|
91
|
+
<option value="off-voice">Off voice — says it in the wrong register</option>
|
|
92
|
+
<option value="slop">Slop — reads as machine-written</option>
|
|
93
|
+
<option value="unclear">Unclear — a person would not know what to do</option>
|
|
94
|
+
<option value="stale">Stale — describes something that changed</option>
|
|
95
|
+
<option value="duplicate">Duplicate — should be said once, in one place</option>
|
|
96
|
+
<option value="typo">Typo or grammar</option>
|
|
97
|
+
<option value="other" selected>Something else</option>
|
|
98
|
+
</select>
|
|
99
|
+
<label class="cd-dialog__label" for="cd-reason">Why? <span class="cd-dialog__hint">The artist reads this instead of guessing. A pointer with no judgement costs them a look and tells them nothing.</span></label>
|
|
100
|
+
<textarea id="cd-reason" class="cd-dialog__text" rows="4" minlength="10" required
|
|
101
|
+
placeholder="What is wrong with it, and what would better look like?"></textarea>
|
|
102
|
+
<p class="cd-dialog__err" id="cd-dialog-err" role="alert"></p>
|
|
103
|
+
<div class="cd-dialog__actions">
|
|
104
|
+
<button type="button" class="btn btn--ghost" id="cd-cancel">Cancel</button>
|
|
105
|
+
<button type="button" class="btn" id="cd-submit">File the flag</button>
|
|
106
|
+
</div>
|
|
107
|
+
</form>
|
|
108
|
+
</dialog>
|
|
109
|
+
|
|
110
|
+
<!-- Closing a flag asks for a note for the same reason opening one does. -->
|
|
111
|
+
<dialog id="cd-close-dialog" class="cd-dialog">
|
|
112
|
+
<form method="dialog" id="cd-close-form">
|
|
113
|
+
<h3 class="cd-dialog__h">Close this flag</h3>
|
|
114
|
+
<p class="cd-dialog__target" id="cd-close-target"></p>
|
|
115
|
+
<label class="cd-dialog__label" for="cd-close-status">Outcome</label>
|
|
116
|
+
<select id="cd-close-status" class="cd-dialog__select">
|
|
117
|
+
<option value="resolved" selected>Resolved — the copy was changed</option>
|
|
118
|
+
<option value="dismissed">Dismissed — looked at, leaving it as it is</option>
|
|
119
|
+
</select>
|
|
120
|
+
<label class="cd-dialog__label" for="cd-close-task">Task that carried the fix <span class="cd-dialog__hint">Optional. This is the ledger link — a copy change ships as a task like every other change.</span></label>
|
|
121
|
+
<input type="number" id="cd-close-task" class="cd-dialog__input" placeholder="e.g. 1003113" min="1">
|
|
122
|
+
<label class="cd-dialog__label" for="cd-close-note">Note <span class="cd-dialog__hint">The next person to notice this sentence reads your note instead of re-litigating it.</span></label>
|
|
123
|
+
<textarea id="cd-close-note" class="cd-dialog__text" rows="3" minlength="10" required></textarea>
|
|
124
|
+
<p class="cd-dialog__err" id="cd-close-err" role="alert"></p>
|
|
125
|
+
<div class="cd-dialog__actions">
|
|
126
|
+
<button type="button" class="btn btn--ghost" id="cd-close-cancel">Cancel</button>
|
|
127
|
+
<button type="button" class="btn" id="cd-close-submit">Close it</button>
|
|
128
|
+
</div>
|
|
129
|
+
</form>
|
|
130
|
+
</dialog>
|
|
131
|
+
|
|
132
|
+
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
133
|
+
|
|
134
|
+
<script src="/builders/shell.js?v=2026-07-02-shell1"></script>
|
|
135
|
+
<script src="/builders/palette.js" defer></script>
|
|
136
|
+
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
137
|
+
<script src="/builders/copy-desk.js?v=2026-08-18-r02"></script>
|
|
138
|
+
</body>
|
|
139
|
+
</html>
|