The Avatar primitive paired bg-secondary with text-foreground, which
isn't a Shadcn-designed pair. In themes whose secondary is a bright
color (Starry Night dark: bright yellow) the global foreground
(near-white) lands on top and the initial becomes unreadable.
Switch to text-secondary-foreground so contrast is whatever the theme
author guaranteed for that pair. Pre-emptively protects any future
theme since every theme must keep secondary/secondary-foreground
contrast valid.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The lightning rail's CTA in PurchaseTicketDialog now reads
"Proceed" (or "Proceed (N tickets)" for multi-quantity) instead
of "Get invoice". Matches the language used on the fiat rails
("Continue to Stripe checkout" etc.) and reads as a generic
forward action regardless of which payment path the user picks.
The empty state rendered "No activities found" twice — once as the
heading and once as the description below, because
`activities.noActivities` and `activities.search.noResults`
translate to the same string in every locale. Drop the description
paragraph (and its mb-1 spacer on the heading).
Adds a search box above the roster list that fuzzy-matches the
holder name and ticket id via the shared useFuzzySearch (Fuse.js)
composable. Empty query keeps the unregistered-first sort intact;
typing reorders by relevance. The empty-state message now
distinguishes "no tickets sold yet" from "no rows matched the
current query" so a busy roster + a typo doesn't look like
backend trouble.
The "Scanned" tab becomes "Tickets" and now lists the full event
roster (sold tickets), not just the registered subset. Unregistered
rows lead the list with a Register button so the host can manually
mark someone present without a QR scan — e.g. lost phone, known in
person, or alternate proof of identity.
useTicketScanner gains registerManually(ticketId), which calls the
same PUT /tickets/register/{id} the scanner uses (so it inherits
the event-ownership gate and the unpaid/already-registered backend
checks), then refreshes stats. It skips the scanner pause + full-
screen banner since the operator initiated the action from the
list, and mirrors the session-local dedup so a subsequent QR scan
on the same ticket reports "Already scanned" instead of a duplicate
register round-trip.
The header now reads "registered / total · N to go" so the host
sees roster progress at a glance; failures from the manual register
surface as a sonner toast and the row reverts.
Reverts 1aeea23 and folds in the actual fix the relocation was
chasing: the Scanner / Scanned tab labels were rendering with
their icons and text mis-aligned because TabsTrigger wraps its
slot in an inline `<span class="truncate">`. A `gap-1.5` on
TabsTrigger never reached the icon/label children. Wrap each
trigger's content in an `inline-flex items-center gap-1.5` span
so the icon and label share a real flex container.
The Scanned / Sold / Remaining strip moves out of the page header
to below the Tabs block. The camera (or scanned list, depending on
the active tab) stays prominent at the top; the counts read as a
summary footer instead of competing with the title for attention.
The stats-error notice follows the counts strip so the warning
stays adjacent to the values it affects.
- Wash out pending/rejected events with opacity-50 + grayscale on a
wrapper div so the operator sees at a glance the event isn't live,
not just the small badge.
- Pull the status badge OUT of the wash-out wrapper and absolute-
position it on Card root (bottom-2 left-2, z-10) so it stays in
full color above the dim card. Both pending and rejected use the
destructive token — the label text differentiates the two states.
Bottom-left so it doesn't collide with the category chip on full
cards or the thumbnail on compact ones.
- Compact rows in the Hosting view now show a small left-aligned
thumbnail (w-20 h-20, self-center, ml-3, rounded-md) when the
event carries an image — host can still recognize each event at a
glance without paying the visual weight of a full hero.
- Card root becomes `relative overflow-hidden`; the wrapper div
owns the conditional flex-row (compact) / flex-col (default)
layout and the opacity/grayscale toggling.
Adds a small filter chip above the month grid that, when on, limits
the calendar to events the signed-in user holds at least one paid
ticket for (intersecting ownedActivityIds from useOwnedTickets).
Hidden when logged out — nothing to own. Left-aligned so it
doesn't collide with the fixed top-right hamburger menu.
State is local to the page on purpose: narrowing the calendar
shouldn't also narrow the feed when the user navigates back.
Hosting feed (ActivitiesPage when onlyHosting):
- Hide the date picker strip + calendar shortcut and the entire
Filters/temporal-pills row; an operator managing their roster
doesn't need calendar navigation or temporal narrowing.
- Keep the search bar — finding a specific event in a long roster
still matters.
- Render compact cards via a new `compact` prop on ActivityList +
ActivityCard: no hero image, single-line title, no summary, no
bookmark, no "Yours" badge (every card is the operator's own),
tighter p-3 padding, single-column flex layout.
Host detail view (ActivityDetailPage when ownedLnbitsEvent):
- Drop the top-bar Scan and Edit buttons. Edit moves into the title
row as a prominent filled-primary icon button right of the title;
Scan moves into the tickets section.
- Render a full-width "Scan tickets" CTA in place of Buy ticket, and
hoist it outside the ticketInfo gate so it appears even on hosted
events that were published without AIO ticket tags.
- Hide BookmarkButton and RSVPButton for the host (favoriting /
RSVPing your own event are noise affordances).
Detail-page badge row: "Yours" leads the row in the highlighted
secondary variant; category and tags drop to outline so the
ownership signal stands out.
useActivityFilters allocated a fresh set of refs on every call, so
when activities-app/App.vue (Hosting bottom-nav tab) and
ActivitiesPage.vue each invoked useActivities(), they got
independent onlyHosting/temporal/etc state. Tapping Hosting toggled
the App.vue ref; the page never saw the change. Hoist the filter
refs to module scope so every consumer shares the same instance.
The bottom-nav tabs become Home, My tickets, Hosting, Map, Favorites.
- Feed is relabeled "Home" (en/fr; es was already "Inicio").
- My tickets and Hosting move out of the sidebar menu back into the
bottom nav. Hosting is a synthetic tab — no path of its own; it
toggles the existing onlyHosting feed filter and lands on
/activities, with Home as the inverse (clears the filter on tap).
- Calendar leaves the bottom nav. The week strip now ends with a
small calendar icon button that routes to /activities/calendar,
so the entry point sits adjacent to the date UI instead of
competing for a tab slot.
- Create activity leaves the bottom nav too. A full-width "+ Create
activity" CTA appears at the top of the feed only when the Hosting
tab is active, so the Create entry point lives inside the section
it belongs to.
BottomTab gains an optional `isActive()` predicate so tabs whose
active condition doesn't reduce to "current path starts with x"
(e.g. Hosting) can compute their own state.
Cards without an image no longer render the solid-color 16:9
placeholder + calendar glyph. They go straight to the content area
with the badges (category, price, Yours, status, Past) shown
inline in a small row at the top, so the title and details aren't
pushed below a meaningless filler block.
The placeholderBg computed (hash → HSL) is removed; it was only
feeding the deleted no-image branch.
Filters icon + Clear-all sit in a stationary left-aligned column;
only the All/Today/Tomorrow/etc temporal pills scroll horizontally.
Clear-all is tucked tightly under the Filters icon (h-5, 10px text,
gap-0.5) and shows only when a filter is active. The badge no
longer lives inside the overflow-x scroll container, so the count
chip isn't clipped at the corner anymore.
Past events no longer gets its own row — it folds into the existing
collapsible (renamed "Filters") alongside Categories, so the feed
gains that row by default. The Filters trigger badge counts past-
events being on plus any selected categories, so users still see at
a glance when hidden toggles are active.
The standalone "Filters active / Clear all" notice is gone too;
Clear all sits inline beside the trigger only when something's
active. Header is tightened (text-xl) and inter-row margins drop
from mb-4 to mb-3 across the date strip + temporal pills.
The top-right "Back to hub" pill in each standalone is replaced by a
hamburger button that opens a right-side sheet reusing the existing
ProfileSheetContent (identity card, back-to-hub link, theme/lang/
currency prefs, profile settings or log-in CTA). The redundant
Profile entry is removed from BottomNav and its loggedOutOpensSheet
plumbing (BottomNav → AppShell) is dropped — Hub.vue still mounts
ProfileSheetTrigger directly so it's unaffected.
ProfileSheetContent gains an `app-nav` slot so standalones can inject
app-specific nav items above the cross-app section. AppShell exposes
a new optional `sidebarNav` prop that forwards items to the menu;
unset on non-activities standalones, those still get the hamburger
menu showing just the shared profile/preferences content.
Activities passes "My tickets" (routes to /my-tickets) and "Hosting"
(toggles the onlyHosting feed filter and lands on /activities), so
those entries leave the inline filter chip row on ActivitiesPage and
live in the sidebar instead. The "Past events" chip stays inline —
it doesn't require auth and pairs visually with the temporal filters.
- Move bookmark heart from top bar to the right of the title.
- Replace the When/Where info cards with caption-style lines directly
under the title (calendar + map-pin icons + muted text).
- Move description above the organizer so it sits right under the
title/info separator; push the organizer card to the bottom.
- Promote the "you own N tickets" CTA (filled primary "View" button)
and demote "Buy another ticket" to outline when the user already
owns tickets, so the My-Tickets path is what jumps out.
- Tighten ticket availability against the buy button: standalone strip
removed, count rendered as an xs muted caption directly under the
buy CTA.
mkWebapp was passing the consumer's `pkgs.pnpm_10` into fetchPnpmDeps,
which means the pnpmDeps snapshot is byte-for-byte different across
consumers using different nixpkgs minor versions (flake's
nixos-unstable has pnpm_10@10.34.0, server-deploy's nixpkgs may have
a different 10.x). The pinned hash matches one snapshot exactly, so
the wrong consumer gets:
ERR_PNPM_NO_OFFLINE_TARBALL @vite-pwa/assets-generator-1.0.2.tgz
at deploy time.
Fix: derive a `flakePkgs` from THIS flake's pinned nixpkgs (via
`flakePkgsFor`) and source pnpm, pnpmConfigHook, fetchPnpmDeps,
nodejs, autoPatchelfHook, stdenv, and stdc++ from it. The consumer's
`pkgs` argument is now used only for its system attribute.
Net effect: the pnpmDeps snapshot is now reproducible regardless of
who's calling mkWebapp. The pinned hash
sha256-FUN2lMHsaBTkk1tljDysYZAoQD+5MIBIEvGnRUWiF4s= remains valid (it
was computed against the flake's own nixpkgs originally).
Verified:
- `nix build .#main` — produces same dist/ as before (uses flake pkgs
internally either way)
- `nix build --impure --expr '...lib.mkWebapp { pkgs = <system>; ... }'`
— now succeeds with the system's nixpkgs, where it would fail
before with NO_OFFLINE_TARBALL on @vite-pwa/assets-generator
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-10 16:19:04 +02:00
1 changed files with 20 additions and 12 deletions