Files
larksuite-cli/internal
niezhiwei 307e753b3f feat(slides): add +update-slide to apply a page of XML by diffing it
Editing most of a page previously meant one block_replace part per
element, each carrying that element's full XML (coordinates, size, font
size included). Restyling every text block on a page was impractical as a
result.

+update-slide takes the page the caller wants, reads the page that is
there, and sends one element-level part per difference. The indirection is
forced: ReplacePart.block_id is validated as a short ELEMENT id (it must
start with "b"), so a single part covering the page is impossible — the
page's own id is "p"-prefixed and the background fill's id is
"f"-prefixed, and both are rejected with 3350001 while element-level parts
on the same page succeed. Element ids are the only handles this endpoint
offers.

--content is the page's target state. An element carrying its original id
is replaced when it differs, an element without an id is inserted at the
position it was written, an element missing from --content is deleted, and
a missing <note> clears the speaker notes. Nothing differing means no
request is sent at all.

Comparison is canonical because the server returns pretty-printed XML with
reordered attributes and injected style defaults the caller never wrote:
attributes are namespace-qualified, quoted and sorted, and indentation
between structural elements is dropped. Text inside a <p> subtree is
compared verbatim and escaped — SML collapses a literal space between
inline tags, but a preserved space written as &#32; decodes to the very
same token, so trimming would drop a real edit as unchanged; and without
escaping, a paragraph holding the literal text "</p><p>" reads identically
to two empty paragraphs. What is sent is the caller's exact bytes, so
their formatting survives into the page.

Anything the diff cannot express fails loudly instead of being silently
dropped: a background change (<style> has no id of its own and its <fill>
id is "f"-prefixed), a reorder of existing elements (there is no move
operation), an id that does not exist on the page (omit the id to create
an element), a root id that differs from --slide-id (the classic
read-page-A-write-page-B mistake), any slide-level structure other than
one <style>, one <data> and one <note>, and container attributes — the
root additionally accepts the SML namespace in the three forms the
repository's SXSD validator recognizes (the official identifier and the
two server read-back spellings), since the primary workflow feeds
+xml-get output straight back in. Unrepresentable structure in --content
is a validation error; a page that comes back with it is a failed
precondition.

Pages carrying an <undefined> placeholder — the server's stand-in for an
object it could not export, such as a whiteboard read without its export
option — are refused outright. Whether the whole-page rewrite behind
slide.replace preserves an untouched placeholder is a server-owned
behavior that no self-contained test can pin down (boards cannot be
created programmatically: the CLI has no whiteboard-create and SML has no
whiteboard element), and editing on an unverifiable assumption risks
silently destroying the one object the caller cannot see. +replace-slide
remains the element-level path for such pages.

--revision-id is documented as what it is. Passing a stale revision is not
rejected by the backend: it means "apply against this snapshot", so
pinning an older one discards every later edit to the page. -1, the
default, is the safe value.

slides:presentation:read sits in the enforced pre-flight scopes, not in
ConditionalScopes: every execution reads the page before writing it, and
ConditionalScopes is metadata only, so a write-only token would reach the
GET before failing.

Also:
- +update as a hidden alias, derived from the canonical shortcut so scopes,
  identities and flags cannot drift. Agents reach for "slide update"
  before reading --help and used to burn turns on the error plus a help
  dump.
- --xml / --slide-xml / --slide-content / --content-xml as hidden spellings
  of --content, scoped to the whole-page commands: --content exists on
  other slides shortcuts, and resolving these aliases there would rewrite
  a mistyped flag into one the caller never meant to use.
- A backend failure reason becomes a typed API error rather than a field on
  a success envelope.
- www.larkoffice.com joins the public-domain allowlist: it is the host of
  the SML namespace identifier, the same vendor domain family as the
  already-listed www.feishu.cn / www.larksuite.com, and the quality gate
  matches exact hostnames.
- Live and dry-run E2E coverage per AGENTS.md. The live workflow walks
  replace, insert, delete and the no-op against the real API, checks a
  background change is refused with the page left untouched and the deck
  order holds, and is self-contained where that is knowable: for stored
  credentials it probes the cleanup scopes with a dry-run delete and
  refuses to create a deck it cannot delete (unexpected probe failures are
  fatal); environment tokens carry no scope metadata, so those runs rest
  on the documented requirement that the CI identity is provisioned with
  the cleanup scopes, and a cleanup failure stays fatal and visible. The
  dry-run suite pins the validation process contract (launch error, exit
  code 2, empty stdout, typed envelope on stderr). An earlier design
  passed every HTTP stub and was rejected outright by the real endpoint;
  coverage.md records that the live test skips without a user token, which
  is the blind spot that allowed it.
- Skill docs: what the command does, what it refuses, why the background
  cannot be changed, and a portable (BSD/GNU) sed spelling in the primary
  example.
2026-07-31 18:20:43 +08:00
..