"Change template" answered "no slide layout" for 28 of the 41 presentations in production: the theme lived only inside the deck JSON, and a presentation whose deck reply failed twice and fell back to markdown slides had nowhere to keep one. The theme is a column now, written at generation and by the picker, and the markdown slide builder carries it to the same renderer field a designed deck uses. A deck's own theme field is kept in step. Articles are the only thing refused — they have no slides. A deck reply that fails to parse is logged with its first 240 characters, so the next "the reply was not a deck" can be read rather than guessed at. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
65 lines
3.7 KiB
JavaScript
65 lines
3.7 KiB
JavaScript
// A deck re-skinned in the library rendered in the default palette anyway. The
|
|
// picker saved the choice correctly and the renderer could apply it correctly;
|
|
// the theme was dropped in between, by the step that puts the drawn figures
|
|
// back onto the slides.
|
|
//
|
|
// attachFigures rebuilds the deck as a fresh object, and a fresh object keeps
|
|
// only the fields it names. It named title, subtitle, date and slides. Three
|
|
// different themes rendered byte-identical files.
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const read = f => fs.readFileSync(path.join(__dirname, '..', f), 'utf8');
|
|
|
|
test('the theme survives the trip to the renderer', () => {
|
|
const src = read('src/utils/documentExport.js');
|
|
const out = src.slice(src.indexOf('var out = {'), src.indexOf('var out = {') + 220);
|
|
assert.match(out, /theme: deck\.theme/,
|
|
'attachFigures drops the theme, so every export renders in the default palette');
|
|
});
|
|
|
|
test('every field the renderer reads off the deck is carried', () => {
|
|
// The failure was one missing key in an object literal, and it was silent.
|
|
// Anything the renderer reads at deck level has to be listed there.
|
|
const src = read('src/utils/documentExport.js');
|
|
const out = src.slice(src.indexOf('var out = {'), src.indexOf('var out = {') + 220);
|
|
for (const field of ['title', 'subtitle', 'date', 'theme', 'slides']) {
|
|
assert.match(out, new RegExp(field + ':'), field + ' is not carried through attachFigures');
|
|
}
|
|
});
|
|
|
|
test('the saved theme is what the library writes, and the renderer reads', () => {
|
|
// Both ends already worked; this pins that they agree on the field name.
|
|
const route = read('src/routes/myResources.js');
|
|
assert.match(route, /deck\.theme = theme/, 'the library writes deck.theme');
|
|
const renderer = read('scripts/render_pptx.py');
|
|
assert.match(renderer, /apply_theme\(/);
|
|
assert.match(renderer, /spec\.get\("theme"\)|deck\.get\("theme"\)|data\.get\("theme"\)/,
|
|
'the renderer reads the theme off the deck');
|
|
});
|
|
|
|
test('an unknown theme leaves the default rather than failing the render', () => {
|
|
const renderer = read('scripts/render_pptx.py');
|
|
const fn = renderer.slice(renderer.indexOf('def apply_theme'));
|
|
assert.match(fn.slice(0, 400), /a deck rendering in the wrong colours beats a deck not rendering/);
|
|
});
|
|
|
|
test('a presentation that fell back to markdown slides still takes a theme', () => {
|
|
// 28 of the 41 presentations in production had no structured deck, and for
|
|
// every one of them "change template" answered that there was no layout to
|
|
// change. The theme is a column now, and the markdown slide builder carries
|
|
// it to the same renderer field a designed deck uses.
|
|
const slideSpec = require('../src/utils/slideSpec');
|
|
const spec = slideSpec.build('% Title\n% Author\n% 2026\n\n# One\n\n- a\n- b\n', { theme: 'ward-teal' });
|
|
assert.equal(spec.theme, 'ward-teal');
|
|
assert.equal(slideSpec.build('# One\n- a').theme, undefined, 'no theme asked for, none invented');
|
|
const route = fs.readFileSync(path.join(__dirname, '..', 'src/routes/myResources.js'), 'utf8');
|
|
assert.doesNotMatch(route, /has no slide layout, so it has no theme to change/);
|
|
assert.match(route, /UPDATE user_resources SET theme = \?, deck = COALESCE\(\?::jsonb, deck\)/);
|
|
assert.match(route, /deck: row\.deck, theme: row\.theme, figureIds/);
|
|
const exporter = fs.readFileSync(path.join(__dirname, '..', 'src/utils/documentExport.js'), 'utf8');
|
|
assert.match(exporter, /slideSpec\.build\(markdown, \{ images: images, theme: options\.theme \}\)/);
|
|
assert.match(fs.readFileSync(path.join(__dirname, '..', 'migrations/1781200000000_resource-theme.js'), 'utf8'), /ADD COLUMN IF NOT EXISTS theme TEXT/);
|
|
});
|