Batches H and I: member area and stations
9. Batch H — Member area tab bodies (member AppShell)
Section titled “9. Batch H — Member area tab bodies (member AppShell)”The member shell, its nav and the Overview tab are in ux-spec.md. These are the bodies of the
other memberTabs() (gating unchanged). Each tab body uses ListLayout slots without the shell header
repeating the space name.
H1. Loans — /member/loans
Section titled “H1. Loans — /member/loans”- Component
MemberActivity.tsx(sectionloans),MemberDashboardSections.tsx(MemberHistory),MemberLoanRenew.tsx. Reference: org2 tabs + rows. title“Loans” ·tabsOut now · History · rows: item, “Due Fri” / “Overdue” pill, inline “Renew” when allowed, history rows with returned date.- Empty: “Nothing out right now. Browse the catalogue to borrow something.” + “Browse catalogue”.
H2. Bookings — /member/bookings
Section titled “H2. Bookings — /member/bookings”- Component
bookings/MyBookings.tsx,BookingReschedule.tsx. Reference: org2 date-chip rows. title“Bookings” ·actionsprimary “Book a space” (→ public bookings) ·tabsUpcoming · Past (replaces the twoaria-pressedbuttons) · date-chip rows (space, time range, status pill) · inline “Move” ·[...]“Cancel booking…” (inline confirm stays, restyled).- Empty: “No upcoming bookings. Book a bench or room when you need one.”
H3. Events — /member/events (+ partner events)
Section titled “H3. Events — /member/events (+ partner events)”- Component
MemberActivity(events),MemberCalendarActions,MemberEventCancel,MemberEventFeedback,PartnerEvents.tsx. Reference: att9 ticket, org2 rows. title“Events” ·tabsGoing · Past · Partner events (only whenPartnerEventshas rows).- Going rows: date chip · title · time · place · inline “Ticket” (opens an att9 ticket card dialog with QR, “Add to calendar”, “Can’t make it? Cancel”).
- Past rows: inline “Give feedback” / “Certificate”.
- Partner events: info line “Your space partners with these hosts, so you can register even when the event isn’t public.” · rows grouped by series · inline “Register” (expands the registration fields in a dialog rather than inline).
- Empty Going: “You’re not registered for anything yet. See what’s on.” + “See events”.
H4. Payments — /member/payments
Section titled “H4. Payments — /member/payments”- Component
MemberPayments.tsx,MemberDashboardSections(MemberDues),payments/PaymentDocuments(member audience). Reference: org2 stats + rows. title“Payments” ·stats: Outstanding (per currency)[loaded]· Membership fee[loaded]· rows: what, due, amount, status pill, inline “Pay now” (online rail) or muted “Pay at the desk” · secondary outline header action “Receipts” (drawer with documents).- Empty: “Nothing to pay. You’re all settled.”
H5. Inbox — /member/inbox
Section titled “H5. Inbox — /member/inbox”- Component
notifications/MemberNotifications.tsx,AnnouncementFeed.tsx,PreferenceCentre.tsx, member notices. Reference: activity rail. title“Inbox” ·tabsNotices · Announcements · Preferences (Preferences usesSettingsLayoutrows: channel switches, digest frequency, Save).- Empty: “No notices need your attention.” / “No announcements yet.”
H6. Account portal — /member/account-portal
Section titled “H6. Account portal — /member/account-portal”- Component
MemberProfilePanel.tsx(+memberProfile/*),MemberCardPanel.tsx(+memberCard/*),MemberDirectory.tsx,MemberReferrals.tsx,MemberCertifications.tsx,MemberClaimCodes. Today these stack in one tall column. - Layout:
SettingsLayoutwith sub-tabs Profile · Card · Certifications · Community · Invite a friend, plus a link chip “Account & security →” (C6). Tab contents depend on modules, as today.- Profile (auth6): rows Avatar (upload), Headline, Institution, Bio, Interests, Languages, GitHub; “Projects” list with dashed “+ Add project”. Save.
- Card (att9 ticket style): a card visual (space colour band, printed name, number, QR when active) · “Name printed on the card” row · photo upload with consent · actions “Save name”, “Preview PDF”, “Save for offline use” / “Forget offline card”, “Keep my loans on this device”. Revoked: danger callout “This card was revoked. Ask staff for a new one.”
- Certifications: rows (machine, level, granted date, expiry pill). Empty: “No training records yet. Ask staff about a machine induction.”
- Community (member directory):
ListLayoutbody: search + member cards (avatar, name, headline, interests chips) → profile drawer (projects, links, recently attended). Empty: “No members have shared a profile yet.” - Invite a friend (
MemberReferrals): org4 “Invite someone” card (email + “Send invite”) + “Pending invitations” list + claim-code field. Empty: “No pending invitations.”
- Split note: Community is a directory, not account content. It belongs in its own member nav item.
That requires adding a value to
MEMBER_TABS/memberTabs(a gating change, which PLAN §5 says to avoid), so it stays a sub-tab here and the decision goes to the owner.
10. Batch I — Stations (KioskLayout)
Section titled “10. Batch I — Stations (KioskLayout)”I1. Event check-in station — /event-check-in/:stationToken, /m/:slug/event-check-in/:stationToken
Section titled “I1. Event check-in station — /event-check-in/:stationToken, /m/:slug/event-check-in/:stationToken”- Component
events/EventCheckInStationPage.tsx+staff/OfflineCheckInOperator.tsx(eventCheckInOfflineStore). - Reference: att10 (PIN pad) + att11 (door scanner) directly.
- Layout:
KioskLayout, two states:- Locked:
stageleft = space avatar tile, “{event} · Door check-in”, big title “Enter the station PIN”, body “Ask an organiser for the 8-digit PIN. It unlocks check-in on this device only.”, 8 PIN boxes.sideright = numeric keypad (1–9, Clear, 0, ⌫) + full-width “Open check-in” (disabled until 8 digits). A physical keyboard still works. Keep theStation PINlabel as the input’s accessible name. Error: “That PIN didn’t work, or check-in isn’t open right now.” - Unlocked:
station bar= avatar, event name, “Door check-in · Station”, counter “{n} / {total} checked in”, Online / Offline pill (queued count when offline: “Offline · 3 waiting to sync”), outline “Lock station” (= today’s “Wipe data and lock station”; confirm “Lock and wipe this device? Unsynced check-ins will be lost.” only when the queue is non-empty).stage= camera frame “Hold the ticket QR inside the frame” + search “Name, email or ticket code” + Find.side= result panel (green “Checked in · {name} · just now” with “Next guest” + “Undo” if the operator supports undo; amber “Already checked in at {time}”; red “Not on the list”), then “Recent” list.
- Locked:
- States: roster downloading: “Getting the guest list ready…” with progress · roster unavailable (400/403/410 wipes): danger panel “This station was turned off. Ask an organiser for a new link.”
I2. Public desk kiosk — /kiosk/:slug (central)
Section titled “I2. Public desk kiosk — /kiosk/:slug (central)”- Component
kiosk/PublicDeskKioskPage.tsx(wrapsPublicInventoryPage). - Layout:
KioskLayoutwhosestagehosts the public catalogue body (core spec’s catalogue, unchanged).station bar= space avatar · “Public desk” · outline “Open full catalogue”. There are no sign-in or theme controls in the bar (the theme toggle moves to a small footer control). Larger touch targets (min-h-12), larger base type. - Note: this is the only kiosk that embeds a core screen. Coordinate with the catalogue owner so the
catalogue body renders without its own
PublicShellheader when hosted here.
I3. Scanner page — /scanner
Section titled “I3. Scanner page — /scanner”- Component
kiosk/ScannerPage.tsx. - Job: a staff device that only resolves QR codes.
- Reference: att11.
- Layout:
KioskLayout:station bar“Scanner” + signed-in staff chip ·stage= ScanWorkbench (§11.2: camera + paste field + Resolve) ·side= resolved-object card (the same card as the staff Scanner tab: “Box B-04 · 12 items”, “Asset · Multimeter #3 · Out”, allowed actions as labelled buttons/pills) · Recent list. - Fix: today the result is a raw
JSON.stringify<pre>. Replace it with the shared result card. Add camera scanning (the staffQrScanneralready exists). - States: idle: “Scan a label or paste its code.” · unknown QR: “That code isn’t one of ours.” · revoked: “This label was revoked.” (from the server message).
I4. Member PWA offline page — public/member-pwa/offline.html (+ offline.css, offline.js)
Section titled “I4. Member PWA offline page — public/member-pwa/offline.html (+ offline.css, offline.js)”- Layout: static HTML that mirrors
KioskLayout’s look in plain CSS. It cannot use React primitives, so PLAN Phase 1 must updateoffline.csswith the new tokens by hand.- Top bar: space name (from the saved card) · “Offline” pill.
- Card visual (as H6 Card) with the saved QR, name, number, and “Saved {date} · last-known”.
- “My loans” list (saved) with due dates.
- Footer: primary “Try again online”, red text button “Forget saved card and loans”.
- Copy: “You’re offline. This is your last-saved card. It may not show recent changes.” · no card: “No card saved on this device. Next time you’re online, save it from your account.”
Staff Scanner tab — tab scanner (listed here because it shares I3’s parts)
Section titled “Staff Scanner tab — tab scanner (listed here because it shares I3’s parts)”- Route
/admin/scanner·panels/ScannerPanel.tsx,ScannerPanelForms.tsx. Gatemanage_qr; modulescanner. - Layout:
AppShell+ListLayoutframe (title “Scanner”, subtitle “Scan or paste a label to see what it is and act on it.”) whoselistslot holds the ScanWorkbench + resolved-object card. Actions on the card: primary depends on target (Box → “View contents”);[...]“Rename & rebind…”, “Move to another space…” (each opens a dialog instead of an inline form), red “Revoke label…” (confirm). The checkout/return hint becomes an info callout with a link: “To lend this, use Direct handout →”.
Part of UX spec: long-tail screens (Space Works UX across all of SpaceWorks), published from docs/ux-screens-longtail.md. Edit it there.