Skip to content

Feature/open source launch - #12

Merged
StarkDroid merged 32 commits into
mainfrom
feature/open-source-launch
Nov 7, 2026
Merged

StarkDroid merged 32 commits into
mainfrom
feature/open-source-launch

Conversation

@StarkDroid

@StarkDroid StarkDroid commented Nov 6, 2026 •

Copy link
Copy Markdown
Owner

Changelog preview

Merging publishes QuickNotch 2.1.0: a minor bump from v2.0.0.

If another pull request merges first, this becomes the next number after it.

Open source launch

Improved

  • Link the source and Ko-fi from the changelog and the tray
  • Patch the vulnerable Vite and sharp versions
  • Rewrite the changelog copy and fix its footer links
  • Give the changelog and download pages a way home
  • Give Ko-fi the real button and thank people after they download
  • Move the support ask off the header and onto the page corner

Bump: release:major (or breaking) for a major, release:patch (or fix, bug) for a patch, anything else for a minor.

This block is rewritten on every push. The text above it is yours.

The open-source release is a good moment to shed API that nothing calls:
unused calendar counting helpers, an unused animation step constant, four
unused palette entries, a superseded footprint accessor on NotchMetrics,
two macOS bridge read-backs, an unused app-cache invalidator, two unused
SystemAccent entry points, the login-item read path (the switch writes
rather than reads) and the SettingsScreen onClose parameter, which the
only call site never used.

The persistence writer now waits on its channel directly instead of
iterating it, which also silences a detekt false positive that reported
the loop variable as an unused property.
Every public address the app points at now lives in one ProjectLinks
object, so the tray, the changelog and the update banner cannot drift.
The changelog footer gains "Source on GitHub" and "Support on Ko-fi"
next to the copyright, the tray gains a support entry, and the footer
text drops "all rights reserved" for the licence the repo actually ships.
QuickNotch is no longer sold, so the site stops selling it. The price
ticket becomes a download ticket: the stub states that it is free and MIT
licensed, the right-hand column lists what the download contains, and the
CTA goes to a real page instead of an external checkout.

`/download` reads the feed the release workflow publishes and builds the
installer URLs from the version it names, so a release needs no site
change. Clicking any download reveals the Ko-fi card once — the only
thing the site asks for, and only after the file is on its way.
`/changelog` renders the same feed under Settings → Changelog, newest
first, grouped by kind. The feed is imported at build time: release
assets send no CORS headers, and importing keeps the version, the history
and the structured data in the prerendered HTML rather than behind a
fetch.

The header and footer carry the version, the changelog, the source, the
issues and the Ko-fi link; the schema declares the software free, MIT
licensed and names the repository, and the generated social card says the
same.
The old README opened with a bare heading and spent its length explaining
a product that is now free. It opens with a banner drawn from
`docs/banner.svg` (rasterised by `npm run banner`, because an SVG loaded
as an image only resolves fonts the viewer happens to own), lists the
states in one table, and keeps install, build, layout, release and
contribution notes short enough that nobody has to scroll past them.

The licence names VelocityApps and keeps the same MIT terms; the badge
row links the latest release, downloads, licence and Ko-fi, and the
closing note points at Ko-fi without gating anything.
The site has a home now: quicknotch.app on Cloudflare Pages. The deploy
workflow builds and type-checks every push that touches `web/`, then runs
`wrangler pages deploy` when the two Cloudflare secrets exist — a missing
secret warns instead of failing, so a fork still gets a green build.

A static page cannot fetch the changelog off the release (no CORS
headers), so the release workflow now commits the feed it already
produced into `web/src/lib/data/changelog.json` on `main`. That push is
what redeploys the site, which keeps the version on the page equal to the
version a visitor can download.

The hosting notes in `web/README.md` and the site section of `agents.md`
describe the two routes, the import-at-build-time rule, the DNS and OAuth
go-live steps, and the one-tour/one-download band rule the page keeps.
`npm audit` flagged three high-severity advisories reachable from the
build: sharp's librsvg dependency below 0.35.5, Vite's launch-editor and
middleware handling up to 6.4.3, and source-map-js 1.2.1. All three sit
inside ranges the manifest already allows, so refreshing the lock file
fixes them without touching a dependency line.

What is left are six low-severity advisories in the SvelteKit toolchain,
all of which need SvelteKit 3; that is a separate, breaking upgrade.
The dialog read like a generated paragraph: an em dash in every sentence,
a "keeps getting better, plenty more in the pipeline" sign-off, and a line
explaining that the notes are not bundled with the build. The description
is now one plain sentence, the update hints are two short ones, and the
feed note is gone.

"You're on the latest version" sat against the left edge under a header
that is otherwise centred; it now spans the card.

Source and Support were 11sp labels in a 2dp box. Anything smaller than
the surrounding text is a hard place to land a cursor, and with no hover
state of their own they never confirmed a tap. They now use the ghost
button chrome ("Try again") with the accent kept on the label, plus a hand
cursor.

They also did nothing when clicked: ProjectLinks called Desktop.browse,
which on macOS returns normally without a browser having opened, so the
only visible effect was the app losing focus. It now goes through
AppLauncher.browse, the `open` hand-off the calendar consent screen
already uses, off the render thread because that call waits on its child.
Both the header mark and the footer mark pointed at `#top`, which on a
subpage only scrolls that page: the version chip in the header leads to
`/changelog`, and from there the logo, the footer and the only "back"
link all stayed put. Three of four links out of those pages were dead.

The marks now resolve to `/`, the footer gains a `Home` entry, its
"Back to top" scrolls the page you are on, and both subpages open with a
`← QuickNotch` link above the eyebrow. The shared link lives in
`app.css` so the two pages cannot drift.
Support was a coffee glyph in the header, a text link in the footer and a
strip that appeared on `/download` with no relation to the click that
caused it. None of that read as an ask.

The header now carries a cream "Ko-fi" pill against the black capsule,
the footer and the download ticket carry Ko-fi's own button artwork
(committed to `static/` rather than hot-linked), and starting a download
opens a native `<dialog>` that thanks the reader and offers the same
button. The dialog is modal, so Escape and focus trapping come free, and
it never intercepts the link: the file is already on its way when it
opens.

The resting capsule widens to fit the pill; under 380px the wordmark
gives way so the three remaining items never clip.
Ko-fi's floating overlay is a script from their CDN that draws its own
chat bubble. Pulling that in would mean a third-party request on every
page and a second visual language on top of the poster one, so the same
idea is drawn here instead: a cream pill with the cup, an ink border and
a hard shadow, fixed to the bottom-right corner of every page that is
for readers.

It waits until the reader has scrolled past the hero and steps aside
when the footer is in view, since the footer already carries Ko-fi's own
button. `/connect` and the OAuth callback are app plumbing and get no
ask. On a phone the label stays in the accessibility tree but only the
cup is painted.

The header goes back to the mark, the wordmark, the version and the
download button. The README drops the shields Ko-fi badge and the extra
text link for Ko-fi's own GitHub button.
…ad cards

The site said the same things in two voices: some copy in `site.ts`, some
inline in components, and a lot of it leaning on em dashes. Every sentence a
visitor can read now lives in `site.ts`, grouped by the surface that shows it:
the chrome, the ticket, the download page, the changelog page, both OAuth
pages, the document SEO, and the values the mock notch and calendar draw. A
copy change is one file.

The em dashes are gone from user-facing copy. Where one joined two clauses the
sentence now uses a colon, a full stop or a comma, which reads like a person
wrote it. `.github/scripts/changelog.mjs` gets the same treatment for the
release titles and PR preview it generates, since that prose is published to
the site and the app.

`/download` and `/changelog` now come back through `← Home` rather than naming
the product twice, and the back link carries the same accessible label as the
header mark.

The Windows card gets one button, matching the macOS card. Both the `.msi` and
`.exe` are built, so the card's note still mentions the `.exe`, but it is no
longer a second link underneath the button: the release page carries the file
for anyone who wants it. That removes the `primary` flag and the `.exe`
installer entry from `releases.ts`, along with the quiet-link markup and its
styles.

Verified: svelte-check 0/0, static build prerendered, verify-render clean on
`/`, `/download` and `/changelog` (no overflow at 1440 or 390, no console or
page errors, no gradients), the thank-you dialog still opens and closes on
Escape, and the built pages contain no em dash.
Both the .msi and the .exe ship with every release, and the app's updater
already handles either: it takes the .msi first, falls back to the .exe, and
the update script branches on the extension. So hiding the .exe behind the
release page made the site look like it published one installer when it
publishes two.

The Windows card now mirrors the macOS one: two buttons of equal weight,
"MSI installer" and "EXE installer", with the file type and architecture under
each. The note underneath says what actually differs, because the file
extension alone does not help someone choosing: the .msi suits managed machines
and silent installs, the .exe is the click-through setup.

The download page lead and its description count installers per platform now,
and the ticket's include line no longer claims one per platform.

Verified: svelte-check 0/0, static build, verify-render clean on /download
(no overflow at 1440 or 390, no console or page errors), both asset URLs in the
built page, and the thank-you dialog still opens on either button and closes on
Escape.
The support ask no longer waits for a scroll. It is a fixed pill in the
bottom-right corner of every page, the app's OAuth screens included, so it
never appears halfway through a read and never disappears at the footer. The
component loses its scroll listener, its IntersectionObserver, the route check
and the hidden state with its tabindex juggling; it is now markup and styles.

Because it is always there, the footer reserves a bottom lane below 900px,
where the last copyright line would otherwise run under the pill.

The platform cards drop the mono caption under their buttons on both Windows
and macOS. A card now ends at the choice itself: the button labels already say
which file each one downloads, with the file type on its second line.

Verified: svelte-check 0/0, static build, verify-render clean on /, /download,
/changelog, /connect and the OAuth callback; the pill is in every prerendered
page, does not move on scroll, and clears the footer at 390 and 820.
The pill in the corner is on every page from the first paint now, so the
footer's copy of the same button only asked twice in the same viewport. The
footer keeps the mark, the tagline, the links and the copyright line, and the
pill carries the support ask everywhere.

Ko-fi's own artwork still appears where it belongs to a moment rather than to
the page: on the download ticket and in the thank-you dialog that opens once a
file starts downloading.

Verified: svelte-check 0/0, static build, verify-render clean on /, /download,
/changelog, /connect and the OAuth callback. The footer reads as brand,
tagline, links, copyright at 1440 and 390, with no overlap from the pill.
"changelog refresh failed: HTTP 404" leaves the reader guessing: a rate
limited API, a repository that is still private, and a release with no feed
all look the same. The URL says which one it was.

Verified: compileKotlinDesktop and detekt pass.
The hosting notes said "attach the domain, add a redirect rule" without the
parts that make those steps work: the SvelteKit preset in the Pages wizard
expects adapter-cloudflare and builds the wrong thing, the www redirect needs a
proxied record before the rule can fire, and `pages deploy` cannot create the
project from CI.

Also states plainly that a private repository serves no release assets, so the
download buttons stay broken until it is public, even if the site itself is up.
The launch runbook carries dates, account steps and half-finished ideas; the
agent onboarding doc is machine-specific context. Neither belongs in a public
repository, so both are ignored, and agents.md is removed from the index (the
file stays on disk).

That leaves a few public files pointing at a doc newcomers cannot open, so the
README now sends readers to the code that documents each subsystem, and the
comments in the build file, main.kt, AppInstaller and the version catalogue
state their own reasons instead of citing a section number.

Verified: compileKotlinDesktop and detekt pass.
Turning the toggle off parked the island far below the top edge. The menu-bar
height helper measured the whole difference between the full screen frame and
the visible frame, and the visible frame also shrinks for a Dock at the bottom:
on this display that returned 120pt instead of the 30pt menu bar, so the notch
landed a fifth of the way down the screen and stayed there.

Measure only the top edge difference. With the toggle off the island now parks
directly under the menu bar, which is the highest a normal-level window can
sit (AppKit clamps it there), and foreground windows can cover it again. It
also stops asking for fullScreenAuxiliary while the toggle is off, so it no
longer rides above a full-screen game or video.

Verified with a throwaway AWT plus bridge probe: the helper reports 30.0 on
this screen, the off-state frame sits at y=30, and toggling alwaysOnTop on its
own never moves a Compose window. compileKotlinDesktop and detekt pass.
The toggle is supposed to change stacking, never position: on, the island
rides above every window; off, the same island at the same place lets app
windows cover it. The previous off state moved it below the menu bar, which is
a visible jump and, worse, measured the Dock as part of the gap.

Level 0 is the problem: AppKit clamps any move of a normal-level window to the
visible frame, so it cannot be parked at the true top. Level -1 is below every
app window and is not clamped. The bridge now raises the window to status level
for the move, sets the frame, and restores the requested level in the same
main-queue turn, so the notch can stay flush with the edge in both states.

The off state also drops fullScreenAuxiliary, so an ordinary window no longer
rides above a full-screen game. Verified with an AWT plus bridge probe: the
window starts at y=30, parks at y=0 at level -1, and is still at y=0 after a
second. compileKotlinDesktop and detekt pass.
The preview fetched the public browser_download_url, which answers a private
repository with 404 and failed the first run against a release that exists.
Fetch the asset by its API URL with the workflow token instead.
The film's project, its captured stills and the rendered MP4 are production
material that lives beside the app, not in it: they would put megabytes of
binaries in every clone and in history. Ignored at the root, like the launch
runbook and the agent notes.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Failed ❌

View logs ↗
a1d444e 2026-11-07T08:39:40.082Z View logs ↗
  • Build: Failed ❌

View logs ↗
867a87c 2026-11-07T07:50:35.260Z View logs ↗
  • Build: Failed ❌

View logs ↗
e6febd2 2026-11-06T21:34:58.012Z View logs ↗
  • Build: Failed ❌

View logs ↗
58e2493 2026-11-06T18:41:12.394Z View logs ↗
  • Build: Failed ❌

View logs ↗
d48046e 2026-11-06T15:47:07.298Z View logs ↗

The static adapter had no error page, so Cloudflare served the homepage
with a 200 for any unknown path. The new /404 route is prerendered to
404.html, which the host answers missing addresses with: one small page,
noindex, unlinked, and two ways back.
Cloudflare now only issues a certificate and a DNS record for a hostname
that belongs to a Worker, so quicknotch.app is served from the quicknotch
Worker: the same prerendered build as static assets, with a twelve-line
handler that sends www and plain http to the apex and the assets binding
everything else. The deploy config leaves the dashboard, the manual
workflow runs wrangler deploy, and the cache from wrangler login stays out
of the repository.
The site now loads Cloudflare's Web Analytics beacon from the layout, with
the site token in site.ts, and the CSP allows exactly that origin for
script and connect. No cookie, no consent banner, no identifier that
follows anyone between sites. /connect and /oauth/* are left uncounted so
the one-time code in the callback URL never reaches an analytics endpoint,
and the FAQ now separates the app, which collects nothing, from the site,
which counts visits.
The secret is now read from the build environment or the ignored
local.properties and written into a generated OAuthConfig source, so no
commit, and no public history, carries a credential. CI supplies it from
GOOGLE_OAUTH_CLIENT_SECRET; a build without one still compiles and says
that it has no Google client instead of starting a consent flow it cannot
finish. Renames the client-type failure to client-credentials, since the
secret, not the client type, is what Google rejects.
Neither half of the OAuth client is in the repository now. The connect
page takes the id from the desktop, which already sends it, so the public
site names no client at all and a fork cannot inherit one it does not own.
The build reads both values from QUICKNOTCH_GOOGLE_CLIENT_ID and
QUICKNOTCH_GOOGLE_CLIENT_SECRET, or from the ignored local.properties, and
Calendar reports a build without them instead of starting a flow that
cannot finish.
Cloudflare's build starts at the repository root by default, where there
was no package.json and no wrangler config, so it stopped at ENOENT before
touching the site. The root now carries both: a package.json whose build
script installs and builds web/, and a wrangler.toml naming web/build as
the assets and web/worker/site.js as the script. The two commands the
dashboard runs are now the two commands that work from a fresh clone, and
the root directory setting can stay empty.
Lenis moved the page on its own schedule and the stylesheet added a smooth
behaviour on top, so wheel input never mapped straight to distance. Both are
gone. Anchor links use scrollIntoView and the header offset from
scroll-padding-top, and the tour follows the reader's scroll instead of
snapping to the nearest step when the gesture ends.
npm ci drops devDependencies when NODE_ENV is production, which would leave
vite and adapter-static out of the Cloudflare build. Both the root build
script and the deploy workflow now ask for them explicitly.
The source had long comment blocks that told the story of each decision:
the probe that was tried, the measurement that settled it, the bug that
came before. The facts they carried are still worth keeping, the narrative
is not. Every comment block of six lines or more now holds at most two
lines of content, decorative section banners are gone, and the detekt
baseline is re-cut because parameter comments are part of a function
signature.
The Workers build for a non-production branch runs npx wrangler preview,
which refuses to start unless the Wrangler config has a previews block.
This one is empty: assets and compatibility settings stay at the top
level, and an empty block is all the command needs to create a Preview.
Ten four-to-five-line blocks still carried three sentences. Each now
carries two, so no comment in the tree carries more than two.
@StarkDroid
StarkDroid merged commit 3b90208 into main Nov 7, 2026
1 of 2 checks passed
@StarkDroid
StarkDroid deleted the feature/open-source-launch branch November 7, 2026 08:42
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