'use strict'; // Hand-written HTML in the Text/HTML widget was rendered far too small to read. // // The Content Designer used to publish absolute sizes as fontSize*10.8 px, and renderText converted // px/108 back to vw to restore the intended size and let those widgets scale. That rescue is // correct — but it ran over EVERY text widget, including markup a person typed themselves. So // `font-size:16px` became 0.15vw: 2.8px on a 1080p screen, 1.9px on a 1280 one. Not clipped, not // hidden — rendered at a size nobody can read, in the one widget whose entire purpose is // hand-written HTML. // // Today's designer emits cqw, not px (frontend/js/views/designer.js), so the conversion only ever // needed to apply to legacy designer output. That is identified by absolutely-positioned elements, // the same signal the dashboard uses to decide whether a text widget can be reopened in the // designer. const { test } = require('node:test'); const assert = require('node:assert/strict'); const path = require('path'); const fs = require('fs'); const os = require('os'); const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'st-textwidget-')); process.env.DATA_DIR = tmp; process.env.JWT_SECRET = 'test-secret-text-widget'; const express = require('express'); const { db } = require('../db/database'); const { requireAuth, generateToken } = require('../middleware/auth'); function seed() { const u = 'u-tw', o = 'o-tw', ws = 'ws-tw'; db.prepare("INSERT OR IGNORE INTO users (id, email, password_hash, role) VALUES (?,?, 'x','user')").run(u, 'tw@test.local'); db.prepare('INSERT OR IGNORE INTO organizations (id, name, owner_user_id) VALUES (?,?,?)').run(o, 'org', u); db.prepare('INSERT OR IGNORE INTO workspaces (id, organization_id, name) VALUES (?,?,?)').run(ws, o, 'ws'); db.prepare("INSERT OR IGNORE INTO organization_members (organization_id, user_id, role) VALUES (?,?, 'org_owner')").run(o, u); return { u, ws }; } const { u, ws } = seed(); function makeWidget(id, html) { db.prepare(`INSERT OR REPLACE INTO widgets (id, user_id, workspace_id, widget_type, name, config, created_at, updated_at) VALUES (?, ?, ?, 'text', ?, ?, strftime('%s','now'), strftime('%s','now'))`) .run(id, u, ws, id, JSON.stringify({ html, background: '#000' })); return id; } const app = express(); app.use(express.json()); app.use('/api/widgets', requireAuth, require('../routes/widgets')); const server = app.listen(0); const token = generateToken(db.prepare('SELECT id, email, role FROM users WHERE id = ?').get(u), ws); async function render(id) { await new Promise(r => (server.listening ? r() : server.once('listening', r))); const res = await fetch(`http://127.0.0.1:${server.address().port}/api/widgets/${id}/render`, { headers: { Authorization: `Bearer ${token}` }, }); return res.text(); } test('THE BUG: hand-written px font sizes must survive untouched', async () => { const id = makeWidget('w-hand', '
Body copy
'); const out = await render(id); assert.match(out, /font-size:16px/); assert.doesNotMatch(out, /font-size:0\.15vw/, '0.15vw is 2.8px on a 1080p screen'); }); test('LEGACY designer output is still rescued, so old widgets keep scaling', async () => { // Absolutely-positioned elements are the designer's signature. 54px was fontSize 5 * 10.8. const id = makeWidget('w-designer', '