commit d401d7d896448d0c33f9522fd87db5e0622f8719
parent 90b052e81724afba70c98cffcbcc30f7e2557829
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Fri, 2 Oct 2026 09:47:31 -0400
report-to-video: the posts feed's header is one row -- the platform and who posted -- with no running count and no caption, and the empty plate says only "No posts yet"; the 30 px the header gave back go to the stack
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
3 files changed, 16 insertions(+), 26 deletions(-)
diff --git a/umtool/report-to-video/README.md b/umtool/report-to-video/README.md
@@ -1300,9 +1300,10 @@ moves: no hold, no footage move, and the cut is as long as its segments.
posts to draw, so a popup schedule and one without posts are byte-identical
to what they were. `postWindows` is empty for a feed.
- **The column** (`chrome-feed.mjs`, one composition for the whole cut,
- region-local at the column, transparent outside its panel): a header (the
- platform and `@handle`, or "Posts" over several authors, a count, "posts as
- the timeline reaches them"), then an empty state until the first post. Each
+ region-local at the column, transparent outside its panel): a one-row header
+ (the platform and `@handle`, or "Posts" over several authors -- no running
+ count and no caption, which a viewer reads off the column itself), then an
+ empty plate ("No posts yet") until the first post. Each
post TICKS IN at the top, newest first: the cards already in slide down by
its height over `push` while it enters from the column's outer edge, its
accent rim flares and settles to a lit rail, which goes out when the next
diff --git a/umtool/report-to-video/chrome-feed.mjs b/umtool/report-to-video/chrome-feed.mjs
@@ -10,7 +10,7 @@
// What the column does
// ---------------------------------------------------------------------------
// Before the first post it shows its header (who posted, on what) and an
-// empty state. Each post TICKS IN at its `in` (deck.mjs postSchedule: its
+// empty plate. Each post TICKS IN at its `in` (deck.mjs postSchedule: its
// clip's start + D, the transition's length -- the first clip's too, which
// has no dissolve into it; several on one clip a `posts.seconds` apart): it lands at the TOP, newest first, as a timeline
// reads, and every card already in slides DOWN by its height. The newest
@@ -63,8 +63,9 @@ export const FEED_MOTION = Object.freeze({
});
/**
- * The column's inner layout, region-local px: the padding, the header's rows
- * and the stack area under it (`stack`: where cards are, and its height --
+ * The column's inner layout, region-local px: the padding, the header (one
+ * row: who posted, on what -- nothing a viewer can't see for themselves) and
+ * the stack area under it (`stack`: where cards are, and its height --
* what `feedCues` fits them to). The card's own sizes: its text, meta and QR
* cell.
*/
@@ -74,7 +75,7 @@ export function feedLayout(render) {
const W = g.column.width, H = g.column.height;
const padX = 22;
const headerTop = 26;
- const headerH = 66;
+ const headerH = 36;
const ruleY = headerTop + headerH + 10;
const stackY = ruleY + 18;
const bottom = 22;
@@ -105,8 +106,7 @@ export function feedLayout(render) {
*
* Card j of n sits at y = Σ (height + gap) of the cards newer than it that
* are in; it is `c<j>` (autoAlpha, x, y), its highlight `h<j>` (opacity), the
- * count `n<k>` (k posts in; autoAlpha), the empty state `empty`, the whole
- * column `col` (x).
+ * empty plate `empty`, the whole column `col` (x).
*
* @returns {{ init: Record<string, object>, gone: Array<number|null>,
* cues: Array<{ k: string, at: number, dur: number, from: object, to: object, ease: string, why: string }> }}
@@ -124,7 +124,6 @@ export function feedCues({
init[`c${j}`] = { autoAlpha: 0, x: enterX, y: 0 };
init[`h${j}`] = { opacity: 0 };
}
- for (let k = 0; k <= n; k += 1) init[`n${k}`] = { autoAlpha: k === 0 ? 1 : 0 };
const ev = [];
const add = (k, at, dur, to, ease, why) => ev.push({ k, at: R(at), dur: R(Math.max(MIN, dur)), to, ease, why });
@@ -152,8 +151,6 @@ export function feedCues({
// The newest hands its highlight on.
if (m > 0 && gone[m - 1] === null) add(`h${m - 1}`, t, calm, { opacity: 0 }, "power1.out", `calm for ${id}`);
if (m === 0) add("empty", t, emptyOut, { autoAlpha: 0 }, "power1.out", `first ${id}`);
- add(`n${m}`, t + lag, MIN, { autoAlpha: 0 }, "none", `count ${id}`);
- add(`n${m + 1}`, t + lag, MIN, { autoAlpha: 1 }, "none", `count ${id}`);
add(`c${m}`, t + lag, enter, { autoAlpha: 1, x: 0 }, "expo.out", `enter ${id}`);
add(`h${m}`, t + lag + glowAt, glowUp, { opacity: 1 }, "power2.out", `glow ${id}`);
add(`h${m}`, t + lag + glowAt + glowUp, glowDown, { opacity: newest }, "power2.inOut", `settle ${id}`);
@@ -264,8 +261,6 @@ export function feedHtml(schedule, render, opts = {}) {
);
})
.join("\n ");
- const countHtml = Array.from({ length: posts.length + 1 }, (_, k) =>
- `<span class="n" data-k="n${k}">${k} of ${posts.length}</span>`).join("");
const vis = deckChoreography(schedule, render).visibility;
const data = {
@@ -321,11 +316,6 @@ export function feedHtml(schedule, render, opts = {}) {
background: ${rgba(pal.accent, 0.26)}; box-shadow: inset 0 0 0 1px ${rgba(pal.accent, 0.7)}; }
.title { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
font-family: 'DeckSansBold', sans-serif; font-size: 26px; line-height: 36px; letter-spacing: -0.005em; }
- .count { flex: none; position: relative; width: 84px; height: 36px; font-size: 16px; line-height: 36px;
- color: ${pal.muted}; font-variant-numeric: tabular-nums; }
- .count .n { position: absolute; right: 0; top: 0; visibility: hidden; opacity: 0; }
- .caption { margin-top: 8px; font-size: 14px; line-height: 20px; letter-spacing: 0.12em; text-transform: uppercase;
- color: ${rgba(pal.muted, 0.9)}; white-space: nowrap; }
.rule { position: absolute; left: ${lay.padX}px; top: ${lay.header.ruleY}px; width: ${W - 2 * lay.padX}px;
height: 1px; background: ${rgba(pal.fg, 0.12)}; }
.stack { position: absolute; left: ${lay.stack.x}px; top: ${lay.stack.y}px; width: ${lay.stack.width}px;
@@ -335,7 +325,6 @@ export function feedHtml(schedule, render, opts = {}) {
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
visibility: hidden; opacity: 0; }
.empty b { font-family: 'DeckSansBold', sans-serif; font-weight: 400; font-size: 20px; color: ${rgba(pal.fg, 0.7)}; }
- .empty span { font-size: 16px; color: ${pal.muted}; }
/* A card: lifted off the column with a touch of the accent, its rail
dim until it is the newest. Hidden until the timeline places it. */
.post { position: absolute; left: 0; top: 0; width: ${lay.stack.width}px; min-height: ${c.plate}px;
@@ -385,13 +374,11 @@ export function feedHtml(schedule, render, opts = {}) {
<div class="who-row">
${who.platforms.map((p) => `<span class="platform">${esc(p)}</span>`).join("")}
<span class="title">${esc(who.title)}</span>
- <span class="count">${countHtml}</span>
</div>
- <div class="caption">Posts as the timeline reaches them</div>
</div>
<div class="rule"></div>
<div class="stack">
- <div class="empty" data-k="empty"><b>No posts yet</b><span>They appear here as the cut reaches them</span></div>
+ <div class="empty" data-k="empty"><b>No posts yet</b></div>
${cardHtml}
</div>
</div>
diff --git a/umtool/report-to-video/chrome-feed.test.mjs b/umtool/report-to-video/chrome-feed.test.mjs
@@ -152,9 +152,10 @@ test("feedCues: each card ticks in at its time, at the top, and the ones in move
// The highlight: the newest flares and settles; the one before it goes out.
assert.deepEqual(byWhy(plan.cues, /^calm for b$/).map((c) => [c.k, c.to.opacity]), [["h0", 0]]);
assert.deepEqual(byWhy(plan.cues, /^settle z$/).map((c) => [c.k, c.to.opacity]), [["h3", FEED_MOTION.newest]]);
- // The empty state leaves with the first post; the count follows each one.
+ // The empty plate leaves with the first post; there is no count to keep.
assert.deepEqual(byWhy(plan.cues, /^first a$/).map((c) => [c.k, c.at, c.to.autoAlpha]), [["empty", 10, 0]]);
- assert.deepEqual(byWhy(plan.cues, /^count z$/).map((c) => [c.k, c.to.autoAlpha]), [["n3", 0], ["n4", 1]]);
+ assert.equal(plan.cues.some((c) => /^n\d+$/.test(c.k)), false);
+ assert.equal(Object.keys(plan.init).some((k) => /^n\d+$/.test(k)), false);
assert.equal(plan.gone.every((x) => x === null), true);
});
@@ -228,7 +229,8 @@ test("the page: one card per post with its date, words and QR; the header; the c
assert.equal((html.match(/class="handle"/g) ?? []).length, 0);
assert.ok(html.includes('<span class="date">Oct 19, 2024</span>'));
assert.ok(html.includes('data-k="empty"'));
- assert.ok(html.includes('<span class="n" data-k="n4">4 of 4</span>'));
+ // No running count and no caption: the header is who posted, on what.
+ assert.equal(/ of 4<|data-k="n\d|class="caption"|class="count"/.test(html), false);
// The cue times are the schedule's.
assert.deepEqual(dataOf(html).posts, s.posts.map((p) => ({ id: p.id, in: p.in })));
assert.equal(dataOf(html).column, feedLayout(FEED).stack.height);