Skip to content

Ask the blocking questions where they arise, cut the index, and finish the button primitive - #5

Merged
datlechin merged 2 commits into
mainfrom
feat/landing-content
Aug 15, 2026
Merged

Ask the blocking questions where they arise, cut the index, and finish the button primitive#5
datlechin merged 2 commits into
mainfrom
feat/landing-content

Conversation

@datlechin

Copy link
Copy Markdown
Member

The four content decisions you asked me to draft, the button migration, and a
script so the cold-start number can stop being a claim.

The two questions that stop a download now sit under the hero

"Is it really free, or free for now?" and "Can I use TablePro at work under
AGPLv3?" were items one and two of a FAQ at position fifteen — behind roughly
three thousand words, past the point where a reader holding either objection has
already left. The AGPL one blocks the highest-value visitor there is: someone
evaluating a Team licence for a company.

"Can the AI drop a table on production?" moves into the Agents section, which is
the section that raises the fear. It was being answered two positions and
several hundred words later.

"Can I move my connections from another client?" is dropped from the homepage —
SwitchFrom is an entire section on exactly that, and since #2 it sits at
position seven rather than eleven.

Nothing leaves the site. home-faqs.ts splits into homeFaqs (the four
that stay in the block) and relocatedFaqs (the four rendered inline), and
/faq spreads both — still fourteen questions, each answer existing in exactly
one place. Verified in the rendered page. Both StaleClaimsTest counts stay
true and keep meaning what they say, so neither assertion needed weakening.

DepthGrid: twelve cells to six

Three repeated a section above it, which makes cutting them deduplication rather
than taste — I verified each: Vim mode against the workbench ledger,
connection import against the whole SwitchFrom section, plugin
verification
against the database grid's own lede. Three more (ER diagram,
query history, import/export) are table stakes every competitor also ships, and
"Import and export, including XLSX" already appears in the pricing comparison.

What survives is the set a reader could not assume: EXPLAIN visualised, server
dashboard, users and roles, Quick Switcher, CSV inspector, backup and restore.

The spec line goes with them — shortcuts, thresholds and defaults, genuinely
useful and genuinely documentation, and roughly 250 words standing between the
reader and the next call to action. The section's own comment called itself "an
index rather than twelve more pitches". It is now an index of six.

/faq gets an h1

It had none at all: SectionShell emits h2, so the page started its heading
tree at level two. sr-only, because the FAQ section already opens with its own
headline and two stacked headlines saying the same thing is worse than none.

Sponsors: structure only

A description slot per sponsor, and the cell becomes a column so a line can sit
under the mark. The copy is not in this PR — it is eight facts about eight
real companies and not mine to invent. A sponsor without one still renders its
mark, so filling them in is a data edit whenever you have them.

The button migration, and two hazards it had to clear first

Nineteen buttons, four padding scales, one primitive nobody imported. They all go
through it now. Neither of these would have been caught by a test here:

  • Download.tsx assigns href imperatively through a ref after resolving
    the real asset URL. A primitive that swallowed the ref would have left both
    download buttons pointing at the generic releases page — a broken download, on
    the download page, silently. React 19 passes ref as an ordinary prop, so the
    primitive takes one.
  • Download.tsx also swaps the two buttons' classes at runtime to make the
    detected architecture primary, and hard-coded both strings. That is the exact
    drift the primitive exists to prevent, reintroduced one layer down.
    buttonClasses() is exported and both halves now read from it.

Three keep their own element and borrow only the class list, because they are
not links: the header's dropdown trigger (aria-expanded, aria-haspopup, a
keyboard handler), the iPhone toggle (aria-controls), and the email form's
submit.

scripts/measure-cold-start.sh

You said the benchmark is measurable, so here is the thing to run. It times
launch to first window for TablePro and any competitor you name, purges the page
cache between every run, and refuses to report if it could not purge — a warm
start quoted as a cold one is precisely what this exercise exists to prevent. It
prints hardware, OS build, app version and the range alongside the median.

The point is not the number. It is that the method is runnable by anyone, which
is what turns "Under 1s" from a claim into a measurement — and whatever lands on
the page should link here.

Give me the output and I will build the EXPLAIN cost tree and the dated
methodology footnote on top of it.

Still outstanding

The hero recapture, which you are doing — that unlocks the Plate component and
the workbench two-state toggles as well. And the eight sponsor lines.

Verification

typecheck, build and pint clean; 96 tests / 1828 assertions with
REQUIRE_SSR=1. Rendered HTML checked for the objection strip, the inline AI
answer, four questions left in the FAQ block, fourteen on /faq, one h1 on
both, and the download buttons still rendering.

Nineteen buttons, four padding scales, one unused primitive. They now all go
through it, and two hazards had to be handled first — neither of which a test
here would have caught.

**Download.tsx assigns `href` imperatively.** It resolves the real asset URL
after mount and writes it onto the anchor through a ref. A button component that
swallowed the ref would have left both download buttons pointing at the generic
releases page: a broken download, on the download page, silently. React 19
passes `ref` to function components as an ordinary prop, so the primitive takes
one.

**Download.tsx also swaps the two buttons' classes at runtime**, to make
whichever architecture it detected the primary one. That hard-coded both class
strings, so the imperative half and the rendered half would drift apart the
first time either changed — which is the exact failure the primitive exists to
prevent, reintroduced one layer down. `buttonClasses()` is now exported and both
halves read from it.

Three buttons keep their own element and borrow only the class list, because
they are not links: the header's dropdown trigger carries `aria-expanded` and
`aria-haspopup` and a keyboard handler, the iPhone toggle carries
`aria-controls`, and the email form's is a submit. Sharing the classes without
forcing the element is the right shape for those.
…he index

Four content decisions, and one script so the fifth can be answered.

**The two questions that stop a download now sit under the hero.** "Is it really
free, or free for now?" and "Can I use TablePro at work under AGPLv3?" used to
be items one and two of a FAQ at position fifteen — behind roughly three
thousand words, past the point where a reader holding either objection has
already left. The AGPL one blocks the highest-value visitor there is: someone
evaluating a Team licence for a company.

**"Can the AI drop a table on production?" moves into the Agents section**,
which is the section that raises the fear. It answered it two positions and
several hundred words later.

**"Can I move my connections from another client?" is dropped** from the
homepage: SwitchFrom is an entire section on exactly that, and it now sits at
position seven rather than eleven.

None of these leave the site. `home-faqs.ts` splits into `homeFaqs` (the four
that stay in the FAQ block) and `relocatedFaqs` (the four rendered inline), and
`/faq` spreads both — so it still lists all fourteen, and each answer exists in
exactly one place. Both StaleClaimsTest counts stay true, and still mean what
they say.

**DepthGrid goes from twelve cells to six.** Three repeated a section above it —
Vim mode from the workbench ledger, connection import from the whole SwitchFrom
section, plugin verification from the database grid's own lede — so cutting them
is deduplication, not taste. Three more were table stakes every competitor also
ships. The `spec` line goes with them: shortcuts, thresholds and defaults are
genuinely useful and genuinely documentation, and they were roughly 250 words
standing between the reader and the next call to action. The section's own
comment called itself "an index rather than twelve more pitches"; it is now an
index of six.

**/faq gets an h1.** It had none at all — SectionShell emits h2, so the page
started its heading tree at level two. sr-only, because the FAQ section already
opens with its own headline and two stacked headlines saying the same thing is
worse than none.

**Sponsors gain a description slot.** Structure only: the copy is eight facts
about eight real companies and is not mine to write. A sponsor without one still
renders its mark, so filling them in later is a data edit.

**scripts/measure-cold-start.sh** measures launch-to-first-window for TablePro
and any competitor you name, purging the page cache between runs and refusing to
report if it could not — a warm start quoted as a cold one is exactly what this
is meant to prevent. It prints hardware, OS build, version and range along with
the median. The point is not the number; it is that the method is runnable by
anyone, which is what turns "Under 1s" from a claim into a measurement.
@datlechin
datlechin merged commit b6cd8dc into main Aug 15, 2026
5 checks passed
@datlechin
datlechin deleted the feat/landing-content branch August 15, 2026 02:43
@datlechin
datlechin restored the feat/landing-content branch August 15, 2026 02:43
@datlechin
datlechin deleted the feat/landing-content branch August 15, 2026 10:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant