Repository navigation
v3.1.0
Added
-
The package ships a
ts-pptx-upstreamskill, andInternalErrorsays where to send a
report. Most code that calls this library is written by an agent working in a repository
that is not this one, and an agent that hits a library defect writes a workaround instead of
filing — which is the rational move from where it stands, since the workaround unblocks its
user today and the tracker belongs to a repo it is not in. So the report never happens and the
next consumer rediscovers the same defect. Three layers answer three different reasons the
report dies.InternalErrornow appends the tracker URL from its constructor rather than from
its throw sites, so a site added later cannot forget it; it is the only class that does, because
it is the only one that already declares whose bug it is, and a "report this" banner on every
malformed package would train callers to skip the line that always means something. The
ErrorCodeTSDoc — and therefore the.d.tsan agent innode_modulesactually reads — now
states the test for the other four: the supported bar is "the output opens cleanly in Microsoft
PowerPoint", and it reads in both directions, so aPackageReadErroron a file PowerPoint opens
cleanly is our gap, not bad input. Andskills/ts-pptx-upstream/is published in the tarball, so
npx skills add ./node_modules/@shbernal/ts-pptxworks offline and always matches the installed
version. The skill's load-bearing instruction is the one about the deck: presentations carry
client names, unreleased strategy and pricing, and the tracker is public, so it spends most of
its length on reducing a failure to a script that builds its own deck — and passes
--repo shbernal/ts-pptxon everyghcall, sinceghin a consumer repo would otherwise file
our bug into theirs. A third issue form,agent-report.yml, is where the error message's URL
lands; its attachment dropdown deliberately has no option for a file containing real data, and
"what should have happened" asks for the reason — an ECMA-376 clause, PowerPoint's own behaviour,
or the docs — since that is what separates an actionable report from a matter of opinion. No API
changed.InternalError.messagegained a trailing pointer, which is not a contract: the class and
thecodeare API and the message never was. -
TableCell.fillNoFillreads an explicit<a:noFill/>on a cell (#7) — the cell-side
counterpart of 3.0.0'sShape.fillNoFill, and whatTableCell.noFill()has always been
able to write.hasOwnFillis not this question: it istruefor any
EG_FillPropertieschild, so it cannot separate a suppressed fill from a gradient, a
pattern, a picture or ana:grpFill, and every colour accessor (resolvedFill,
fillColor,fillSchemeColor) reportsnullfor a no-fill cell exactly as it does for
one inheriting the style's shading. Deriving it as "has an own fill that no accessor
recognises" also changes meaning silently the day a further fill kind gets an accessor.
Changed
-
Development toolchain: ESLint + Prettier → oxlint + oxfmt, and TypeScript 6 → 7.
This is a contributor-facing change with no runtime effect on the published package —
no API was added, removed or altered, and the generated OOXML is byte-identical across
all 183 parts of the reference decks. Enforcement was held level rather than relaxed: of
the 90 lint rules previously enabled onsrc/, 89 carry over (the missing one,no-octal,
is already a syntax error in an ES module), type-aware linting stays on, and
no-floating-promises/no-misused-promisesnow also coverscripts/andtest/,
where the old configuration could not afford them. Seedocs/development.md. -
Published
.d.tsfiles are textually different, though no type changed. TypeScript 7
prints declarations slightly differently: string literal types use single quotes
(readonly shapeType: 'autoShape'rather than"autoShape") and redundant parentheses
are dropped (fontRef?: (StyleFontRef | null) | undefinedbecomes
fontRef?: StyleFontRef | null | undefined). Chunk filename hashes shift as a
consequence. No declaration, signature or export moved, andpublint,
@arethetypeswrong/cliand the packed-package smoke test all pass — but a consumer
diffing shipped declarations between versions will see churn, which is why it is recorded
here rather than left as an implementation detail.Note that documentation generation deliberately stays on TypeScript 6: TypeScript 7 is
the native Go compiler and ships no JavaScript compiler API for TypeDoc to import. That
copy is confined to the privatetools/api-docsworkspace package and reaches nothing
that is published.
Fixed
-
fill: { type: 'none' }emits<a:noFill/>instead of nothing at all (#9). The
option's own name states the shape is transparent, and it was the one call that did not
produce that state:genXmlColorSelectionhad nononecase, so the fill child was
omitted entirely and the interior fell back top:style/a:fillRefor the placeholder —
a shape carrying a style reference rendered in the theme's accent colour rather than
transparent.line: { type: 'none' }on the same options object has always emitted its
a:noFill; the two now agree. This reaches every caller of the shared fill dispatch, so
a table cell (TableCellProps.fill) can author a transparent cell the same way.
Round-trip consequence:addShape({ fill: { type: 'none' } })→ save → load now reports
Shape.fillNoFill === true, where it reportedfalsebefore. The one behaviour that
changes for existing decks is thattype: 'none'no longer produces the inherit state
by accident — if you were relying on it to mean "leave the fill to the style", omit the
filloption instead. -
readModelToIrcarries a line's@cap, and declares a dropped@algn(#8). Both
legs of the cap mapping already existed —ShapeLineProps.capauthors the attribute and
3.0.0'sShape.lineCapreads it back — but the script tier'slineOptionnever consumed
it, so a deck this library wrote could not survive its own converter. The loss was
undeclared, which is the part that mattered: the round-trip gate excludes exactly what
a fidelity note names, so it passed green while the deck changed.@capextends every
dash by the stroke width and decides whether each draws as a rectangle or a lozenge, so
on a thick dashed rule the before and after are visibly different.@algnis readable and
has no write option, so it now records aline.alignnote (dropped/unwritable) —
foralgn="in"only, sincectris what an omitted@algnalready renders as. -
latexToOmmlemits accents as<m:acc>rather than<m:limUpp>(#6).\hat,
\bar,\vec,\dot,\ddot,\tilde,\acute,\grave,\check,\breve,
\mathring,\H,\dddotand their short-form aliases (\^,\`,\',\",
\.,\=,\u,\v,\r) all landed as over-limits, with limit spacing and
semantics, because temml emits a bare<mover>(correct for a browser — MathML renderers
derive accent positioning from the operator dictionary) and mathml2omml has no dictionary
and keys strictly offaccent="true". The pipeline now carries the small dictionary
subset that closes the gap, and while it is there it swaps temml's spacing modifier for
the combining mark ECMA-376 §22.1.2.20 says anaccPrcharacter should be — so\vec{v}
gets an arrow accent instead of a full-size arrow hung over the base.Scoped to
latexToOmml:mathmlToOmmlpasses hand-written MathML through unchanged,
because thereaccentis the caller's to set. Constructs that were already mapping well
are untouched (\widehat/\overbracestaym:groupChr,\overline/\underlinestay
m:borderBox,\stackrelstaysm:limUpp), and two stay limits by necessity:\utilde
and other under-accents, since OMML has no under-accent object and the symmetric
accentunder="true"makes mathml2omml emit an over-accent; and\ddddot, whose
two-character operator has no singlem:chr. -
A table cell's explicit
a:noFillsurvives read → script → write. Previously a
suppressed cell fell out ofcellFillas "no fill option" and the copy took the table
style's banding — the opposite of what the source showed. Enabled by the
TableCell.fillNoFillreader above and thefill: { type: 'none' }writer fix above.