Properties were listed by name, so a rename moved one. Components now keep prop_order (the names as they were made; a rename keeps the place) and every listing follows it. Make slot opens a name field under the frame's Slot row; Enter makes the slot, Escape cancels, a taken name keeps the field open.
- The entry size cap is figcraft_doc::ImageData::MAX_BYTES (256 MiB), the
cap the .fig, .sketch and REST importers already read images with (now
one constant), instead of 32 MiB: an imported image larger than 32 MiB
made the document unjoinable and its server snapshot unrestorable.
- History written before the checks (a restored or joined snapshot, a
replayed server log, a file published to a server) applies without the
image entries they refuse (docprops::without_refused_images), instead of
the file not opening or a restart dropping every edit after the image.
- A key that already holds an image can't be given other bytes: producers
key images by their bytes (the REST importer's placeholder keys always
hold the same picture), and merge treats a key's image as never
changing.
- REST import: an unresolved imageRef shaped like a content hash shares
the rest-image-missing placeholder (the placeholder's bytes don't hash to
it); ImageData::claims_content_hash is shared with collab.
- Tests: collab image_tests (4 new, size test uses a small cap), format
image_tests (1 new).
- Docs: collab-protocol.md, collaboration-parity.md, file-format-parity.md.
Image entries applied bytes to any key with no checks (audit COL-9): a
collaborator could poison the shared content-addressed image store, claim
absurd dimensions, balloon memory, or name an image `../../x`, which a
saved file then wrote as the archive member `images/../../x`.
- Every key must be 1–128 ASCII letters, digits, `-` or `_`:
`figcraft_doc::ImageData::is_valid_key`, promoted from the format
crate's private `is_valid_hash` so the file reader and collab share one
rule. The reader now also takes `-` and `_`, so the REST importer's
`rest-image-missing` placeholder survives a save and load.
- The REST importer's side of the same attack: an unresolved `imageRef`
that can't legitimately be an archive member name (path text, an
over-long ref) shares the `rest-image-missing` placeholder instead of
being keyed by its raw name
(`image_tests::a_path_like_rest_image_ref_never_names_an_archive_member`).
- A key with the shape of a content hash (64 hex chars) must be exactly
the lowercase sha256 of the bytes, the form `image_hash` writes; an
uppercase look-alike is refused. Other keys (svg FNV-128, REST
placeholders) claim no content address and only meet the rules above.
- A side is at most 65 535 px; decoded bytes at most 32 MiB, with the
base64 text capped before decoding. 0×0 stays valid: the format crate
stores it for sizes it can't probe.
- Refusals are Error::Invalid and reject their batch, so every replica's
store stays clean; catch-up snapshots apply through the same check.
- Docs: collab-protocol.md, collaboration-parity.md and
file-format-parity.md (rows, status lines, histories).
- Tests: collab image_tests (8), format image_tests: round-trip and the
REST placeholder gate.