feat(assets): the panel that operates the client-file imports (Phase 8)
Some checks failed
PR Checks / frozen-manifest (pull_request) Successful in 1m3s
PR Checks / server-tests (pull_request) Successful in 8m4s
PR Checks / client-build (pull_request) Failing after 14m21s

Admin -> Client Files: one page over the three things that come out of the
operator's UO client -- creature portraits, item and land pictures, and the
cliloc table. One page rather than three because they are one job: same client
install, same bridge, and all of them change at the same moment, when the
operator patches that client. Boot never asks the shard for any of it, so these
buttons are the only thing that imports.

The cliloc pair had had no UI at all since phase 2. On a bridged install, where
boot deliberately stopped calling the shard, that meant `curl` was the only way
to load 67,496 names.

Update and Re-import everything are section 6's two stages as two buttons rather
than one button and a checkbox, because they cost wildly different things. A
vanished key is reviewed in the page and not in a table -- an asset import only
happens because someone pressed a button here, so the review is already in front
of the person who caused it -- and it shows each key's PICTURE, since
`body/820/a23` names nothing a human recognises. `shard_asset_meta` gained a
`last` block (what the import did, who ran it) so the panel can answer "did last
week's import do anything" without scrolling core's whole activity log.

The live walk against a real shard imported 1,095 portraits in 3.5 s, warmed 313
item pictures in 0.6 s and reloaded 67,496 cliloc rows in 1.7 s -- and found two
DELETIONS that predate this phase and that no test could see, because only a
screen showing the numbers together makes them visible:

  * The body import diffed its manifest against every family's rows. Phase 5 put
    item and land art in the same table, and a body manifest never mentions
    them, so all 313 item pictures were staged for deletion with a sentence
    saying the shard had stopped offering them.
  * An approved vanish unlinked the sprite and kept the row. The catalogue went
    on counting a picture that was gone, the atlas could point a creature page at
    a missing file, and the next forced import offered the same key for review
    again -- reporting "nothing was changed" about a file it had deleted.

Both fixed here, with the removals now inside `saveAssets`'s own transaction.
The same whole-table read made the panel announce a 1,408-row creature catalogue
on an install holding 1,095 portraits and 313 item pictures.

Protocol stays 8 and EXTRACTOR_VERSION stays 3: nothing on the wire changed.

Refs: docs/link/v8.md sections 12.2, 14, 16 (phase 8)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
This commit is contained in:
2026-09-14 08:10:16 -05:00
parent 7dbdaa14ad
commit 675e879b48
13 changed files with 1451 additions and 54 deletions

View File

@@ -8482,6 +8482,23 @@
"properties": {
"type": "object",
"properties": {
"linked": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"description": {
"type": "string",
"example": "Whether a shard is configured and enabled at all. Stated rather than inferred: `shard: null` is also what a linked shard that is merely DOWN looks like, and the two want opposite things from an admin surface — one disables its import buttons, the other keeps them available so the operator can retry."
},
"example": {
"type": "boolean",
"example": true
}
}
},
"loaded": {
"type": "object",
"properties": {
@@ -8656,6 +8673,253 @@
"example": 0
}
}
},
"last": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"nullable": {
"type": "boolean",
"example": true
},
"description": {
"type": "string",
"example": "What the last import actually did. NULL on an install that has never imported, and on one whose last import predates this field — both of which mean \"no import recorded\", which is a different answer from an import that fetched nothing. The admin activity log records the same action, but it is one unfiltered list of every admin action on the site, so an import from three client patches ago is not findable there."
},
"properties": {
"type": "object",
"properties": {
"at": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "date-time"
}
}
},
"by": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"nullable": {
"type": "boolean",
"example": true
},
"description": {
"type": "string",
"example": "The admin who pressed it, from their session."
}
}
},
"force": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"description": {
"type": "string",
"example": "True when it was a full re-import rather than an update."
}
}
},
"approve": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"description": {
"type": "string",
"example": "True when it accepted assets the shard had stopped offering."
}
}
},
"assets": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 1095
}
}
},
"fetched": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 12
}
}
},
"written": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 12
}
}
},
"removed": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 0
}
}
},
"absent": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 0
}
}
},
"unsupported": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 0
}
}
},
"bodies": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"nullable": {
"type": "boolean",
"example": true
},
"description": {
"type": "string",
"example": "The body pass, as a tally rather than one number: `unknown` is real drift — a spawn file naming a type this shards scripts do not define — and reads identically to a failure if both are summed into \"not resolved\"."
},
"properties": {
"type": "object",
"properties": {
"ok": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 780
}
}
},
"unknown": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 20
}
}
},
"notCreature": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 12
}
}
},
"failed": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 0
}
}
}
}
}
}
},
"art": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"description": {
"type": "string",
"example": "Creatures pointing at a picture afterwards."
},
"example": {
"type": "number",
"example": 763
}
}
}
}
}
}
}
}
}
@@ -9271,18 +9535,57 @@
"type": "boolean",
"example": true
},
"description": {
"type": "string",
"example": "On `needsReview`: up to fifty of the keys that disappeared, each with the picture this site currently serves for it. The filename is there because the decision being asked for is \"is it right that these disappear?\", and an asset key names nothing a human recognises — `body/820/a23` is a horse."
},
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"key": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "body/820/a23"
}
}
},
"file": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"nullable": {
"type": "boolean",
"example": true
},
"description": {
"type": "string",
"example": "Filename under uploads/atlas/, or null if this site never stored a picture for it."
},
"example": {
"type": "string",
"example": "uo-body-820-a23-9f3c1a77.png"
}
}
}
}
}
}
},
"description": {
"type": "string",
"example": "On `needsReview`: up to fifty of the keys that disappeared."
}
}
},