Repository navigation
v3.7.0
[3.7.0] - 2026-08-30
groupObjects() has always addressed objects by objectName, and there has never been a
way to learn those names. That is fine while one caller authored everything and useless
the moment a slide is assembled by independent renderers, which is exactly the case
groupObjects() was added for: nobody kept the descriptors, so the only handle on what is
on the slide is a name nothing reports. The two ways out were both bad — make every
renderer surrender its internals, or keep a parallel ledger that is wrong the first time a
renderer adds an object it did not announce. slide.objects is the accessor that was
missing, and it reports the groupable-kinds rule alongside each object so a consumer does
not pin a second copy of it.
This is also the first release to go out under two names. pptx-ts is the same build
published a second time, for people who cannot or will not type a scope; @shbernal/ts-pptx
stays canonical, and installing both at once is the one thing not to do.
Added
-
The package is published under a second, unscoped name:
pptx-ts. npm has one
package per name and no notion of a redirect, so an alias on the registry is not a
pointer at another package: it is a second publish of the same content under a second
name. That is what this is.scripts/alias-package.mjsstages a copy of the build the
release gates just proved, and.github/workflows/publish.ymlpublishes that copy after
the canonical publish has succeeded. Three things differ from@shbernal/ts-pptxat
the same version, and nothing else does:package.json#name, aREADME.mdbanner
naming the scoped package as the canonical one, and the absence of thescriptsblock,
which is dev-only metadata in a published manifest and whoseprepackwould delete the
stageddist/if a publish ever ran without--ignore-scripts.version,exports,
files,dependenciesand thedist/andskills/payloads are the same bytes, so
the alias is the package the gates measured rather than a near-copy of it.Install one or the other, never both. Two copies of this library in one dependency
tree are two module registries, and per-copy state such as the diagnostic handler stops
behaving as one library. The scoped name stays canonical: the issue tracker, this
changelog and every example in the docs use it, and nothing about cutting a release
changes because the second name exists.The name is reversed rather than scope-stripped because
ts-pptxon npm belongs to an
unrelated project. -
slide.objects: the authored objects on a slide, bottom-to-top in z-order. The
read-back half ofgroupObjects(). Each entry is aSlideObjectInfocarryingtype,
objectName,isPlaceholder,canGroup, andchildren(a group's members, nested to
any depth). It is a snapshot rather than a live handle: a fresh array on every access,
inert, and the way to act on it is to call the authoring API with the names it reports.objectNamecomes back in the caller's spelling, not the stored one. Every
add*Definitionstores the name attribute-escaped andgroupObjects()escapes the
caller's spelling before comparing, so reporting the stored form would hand back a name
that escapes a second time on the way in and resolves to nothing — the same mismatch
1.0.0 fixed on the input side, arriving from the other direction. A shape namedQ&A
reads back asQ&A. The promise is the round trip rather than invertibility, and it
holds even where decoding is not a true inverse: a name authored as the literal string
&decodes to&and re-encodes to the stored&.objectNameis always a string. An object authored without one still carries the
generatedShape 3/Text 1/Group 2identity PowerPoint shows in the Selection
Pane, and that name addresses it as well as an authored one does — reportingnullthere
would have withheld a usable handle to signal a distinction nothing downstream of
authoring records anyway.canGroupis the predicategroupObjects()throws on, not a restatement of it.
GROUPABLE_TYPESand the placeholder exclusion now live behind oneisGroupableObject
that both the accessor and the throw path call. A consumer that had to re-state the rule
would be pinning a copy of a list it does not own: it would keep refusing the day
grouping learns a new kind and go on offering the day one is withdrawn, with nothing
failing in either direction. It answers about the object and not the call — an
unresolved, duplicated or ambiguous name still throws, and no single object can speak
for a selection.
Changed
- The publish workflow is re-runnable when only one of the two names went out.
Publishing two packages is not atomic, so the canonical publish can succeed and the
alias fail after it. The unpublished-version guard therefore fails only when both
names already carry the version, and each publish step is skipped when its own name
already does. Re-dispatching the workflow on the same tag finishes the missing half and
touches nothing else. Previously the guard failed whenever the version existed under the
one name it knew about, which would have madeworkflow_dispatchuseless in precisely
the state it exists for. The alias is published last for the same reason: a failure in
it costs a re-dispatch, never the release that already went out.