AGPL-3.0-only, a source link in the app, and everything on dev since #5 #6

Merged
bvandeusen merged 214 commits from dev into main 2026-10-21 21:40:00 -04:00
3 changed files with 36 additions and 27 deletions
Showing only changes of commit ef418a8c92 - Show all commits
+7 -9
View File
@@ -11,18 +11,16 @@ withDefaults(
</script>
<template>
<!-- The look comes from `.btn` + a variant in style.css, NOT from here. A
download has to be an <a> (only an anchor can carry an href and hand the
transfer to the browser), so the shape has to live somewhere both elements
can wear it. The disabled: variants stay local — an anchor has no
:disabled, so they are not shared and never were. -->
<button
:type="type"
:disabled="disabled || loading"
class="inline-flex items-center justify-center gap-2 rounded-lg px-4 py-2.5 text-sm font-semibold transition
focus:outline-none focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-2
focus-visible:ring-offset-neutral-50 dark:focus-visible:ring-offset-neutral-950
disabled:cursor-not-allowed disabled:opacity-60"
:class="
variant === 'primary'
? 'bg-brand text-neutral-900 shadow-sm hover:bg-brand-600 active:bg-brand-700'
: 'text-neutral-700 hover:bg-neutral-200/70 dark:text-neutral-200 dark:hover:bg-neutral-800'
"
class="btn disabled:cursor-not-allowed disabled:opacity-60"
:class="variant === 'primary' ? 'btn-primary' : 'btn-ghost'"
>
<span
v-if="loading"
+11 -8
View File
@@ -143,17 +143,20 @@ function readableSize(bytes: number): string {
>
</p>
</div>
<!-- A plain anchor, never BaseButton and never a fetch: these are 3–95 MB
and the browser's own download manager handles the transfer better
than anything this app would do with a blob. `download` carries no
filename because the server already names the file in its
Content-Disposition, which browsers prefer over this attribute
anyway — a value here would be inert and read as if it weren't. -->
<!-- An anchor, never BaseButton and never a fetch: these are 3–95 MB and
the browser's own download manager handles the transfer better than
anything this app would do with a blob. It wears `.btn` — the same
definition BaseButton wears, so the two cannot drift.
`download` carries no filename because the server already names the
file in its Content-Disposition, which browsers prefer over this
attribute anyway — a value here would be inert and read as if it
weren't. -->
<a
:href="client.url"
download
class="btn-link shrink-0"
:class="group.prominent ? 'btn-link-primary' : 'btn-link-ghost'"
class="btn shrink-0"
:class="group.prominent ? 'btn-primary' : 'btn-ghost'"
>
Download
</a>
+18 -10
View File
@@ -253,25 +253,33 @@ body {
@apply inline-flex min-h-[2.25rem] items-center justify-center px-3;
}
}
/* A DOWNLOAD is an anchor, never a button. These are 3-95 MB files, and the
* browser's own download manager handles the transfer better than anything this
* app would do with a blob - but only an <a> can carry an href, and BaseButton
* is a <button>. So the button SHAPE lives here where an anchor can wear it.
/* THE button shape — the ONE definition of it in the app.
*
* Declarations mirror BaseButton.vue exactly, minus its disabled: variants (an
* anchor has no :disabled). The two are a pair: changing the look of one without
* the other is how a page ends up with two kinds of primary button.
* It lives here, in the components layer, rather than inside BaseButton.vue,
* because not every button in this app is a <button>. A DOWNLOAD has to be an
* anchor: these are 3-95 MB installers, only an <a> can carry an href, and the
* browser's own download manager handles that transfer better than anything the
* app would do by fetching to a blob. BaseButton cannot serve that case, and a
* second copy of its class list for anchors is how a page ends up with two
* kinds of primary button that drift apart.
*
* So: BaseButton.vue wears these, and so does any anchor that must read as a
* button. Neither owns the look.
*
* The `disabled:` variants are NOT here on purpose — an anchor has no
* :disabled. BaseButton adds them itself, which is exactly the split: shared
* where it is shared, local where the element differs.
*/
.btn-link {
.btn {
@apply inline-flex items-center justify-center gap-2 rounded-lg px-4 py-2.5 text-sm
font-semibold transition focus:outline-none focus-visible:ring-2 focus-visible:ring-brand
focus-visible:ring-offset-2 focus-visible:ring-offset-neutral-50
dark:focus-visible:ring-offset-neutral-950;
}
.btn-link-primary {
.btn-primary {
@apply bg-brand text-neutral-900 shadow-sm hover:bg-brand-600 active:bg-brand-700;
}
.btn-link-ghost {
.btn-ghost {
@apply text-neutral-700 hover:bg-neutral-200/70 dark:text-neutral-200 dark:hover:bg-neutral-800;
}
.nav-link {