The Policies tab gets the redesign Grant asked for: replace the
table view + verbose disclosure form with a card grid where each
existing policy renders as a buy-page-style tier card sitting next
to a dashed "+ Add tier" placeholder. Click the placeholder, it
morphs into an editable draft tier card with inline form fields;
submit Create on the card and it flips into a read-only preview.
Multiple drafts can coexist for parallel multi-tier authoring with
side-by-side comparison.
New JS helpers:
- helpIcon(text) — small "?" hover tooltip for compact form labels
- slugify(s) — URL-safe slug derivation from display name
- renderTierCard(pol, product, onMutate) — read-only buy-page-style
preview card with Edit / Hide-Show / Delete actions
- renderAddTierCard(onClick) — dashed placeholder with "+" affordance
- renderDraftTierCard(product, onCommit, onCancel) — inline editable
card with name + slug + price + duration + entitlement bubble
picker + recurring/trial toggles
- renderPolicyCardGrid(product, policies, byPolicyCounts, onMutate) —
ties them together. Submitting "+ Add tier" appends a fresh
placeholder, so operators can keep clicking to author multiple
tiers in one session.
formInput() upgraded:
- New `help:` option renders a helpIcon next to the label (replaces
verbose hint text under the input)
- New `placeholder:` option for cleaner empty-state cues
Auto-slug:
- Product create form's Display name field mirrors a slugified
version into the Slug field as the operator types — until they
manually edit the slug, which arms a "userOverridden" guard so
manual edits stick. Re-arms when the slug field is cleared.
Legacy "Create a new policy" disclosure form unsurfaced from
the Policies route — the card grid replaces it. Advanced fields
(custom grace seconds, tip recipient, tier rank) still live on the
existing Edit modal of an already-committed tier card. Power-user
flow: card grid creates the basics, Edit modal refines.
Test count unchanged (78). UI-only release.
Notes cover the entitlements catalog feature shipped in 68dfe7f
plus the four SDK 0.3.0 cuts (TS / Rust / Python / Go) that
surface the catalog on listPublicPolicies. Phase 2 (side-by-side
card-grid policy authoring UI) is queued for v0.2.0:9.
KEYSAT_INTEGRATION.md section 8 grows a subsection explaining the
catalog mechanics: bubble picker, buy page rendering, SDK surface,
catalog-stability rule.
Test count: 78 (unchanged from :7 except for migration_0014 already
counted in the prior commit).
The Start9 registry card was still showing "Keysat — self-hosted
Bitcoin-paid software license server" while keysat.xyz now leads
with "Bitcoin-native self-hosted licensing service for software
creators." Operators landing on the registry from the marketing
site got a jarring tagline mismatch.
Aligned everywhere the old copy was hardcoded:
- startos/manifest/i18n.ts (short + long descriptions — these
drive the registry card)
- assets/ABOUT.md (in-StartOS About panel)
- README.md (root + licensing-service/)
- licensing-service/Cargo.toml description
Long description also picked up two updates that should have
landed when the features did but never made it into the marketing
copy:
- Zaprite mention (Bitcoin + cards) alongside BTCPay
- Recurring subscriptions + in-place tier upgrades
Pure copy change. No code, no behavior, no schema. Republishing as
:7 because the registry card text lives inside the .s9pk and
won't refresh on operators' boxes without a version bump.
Bump with notes covering the active_payment_provider preference,
the new Activate <provider> actions, and the symmetric Disconnect
handling.
Test count: 42.
Bump to v0.2.0:2 with notes covering Zaprite as second payment
provider, migration 0011 (recurring subs schema dormant), 0012
(zaprite_config). Test count 41.
Per operator feedback: the discount-code field on /buy/<slug> was
showing 'FOUNDERS50' as a placeholder, which confused buyers (some
tried it as a real code, some assumed Keysat shipped a default
discount). Empty placeholder now; buyers paste their actual code.
No semantic change. Wrapper-only revision; daemon binary unchanged
beyond the embedded HTML template.
Adds startos/versions/v0.2.0.ts as a draft milestone version entry,
ready to swap in as `current` when we're ready to cut. NOT yet wired
into the version graph at versions/index.ts — flipping that switch
is a release decision (one-line change there, then make x86 +
publish), and the draft sits parked so we can iterate on the
release-notes content without committing to the cut.
Format note: the SDK's VersionInfo.of() expects releaseNotes as a
LocaleString (Record<string, string>), not the string[] form
v0.1.0.ts uses. The new file uses the modern shape; v0.1.0.ts keeps
its existing form to avoid churn on the alpha line.
CUTTING_V0.2.0.md walks the operator (or future me) through the
4-step cutover: edit versions/index.ts to swap in v0_2_0, npm run
check, make x86, publish. Plus rollback notes if anything goes
sideways post-cut.
Why park rather than cut now:
1. The user said "prepare for the version 0.2 plumbing" — that's
"prepare" not "do". The cutover is intentional in the user's
workflow, not bundled into a routine push.
2. Cutover changes how the StartOS marketplace renders the upgrade
dialog to existing :N installs; best to QA the release-notes
content first.
3. SDK migration-API behavior on the upstream version bump is
worth verifying on a test install before flipping for everyone.
The v0.2.0 release notes themselves are written conservatively —
they describe what's already shipped and stable in the alpha line
through :47, not aspirational v0.3 features.