0108a70131d8a05617da0e483e466f1ed2b8c79c
2 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0551e8dd6e |
Add link previews and icons; make PIG_INVITE_CODE actually work
Social and icons. A 1200x630 card, apple-touch-icon, and maskable PWA icons, generated from an HTML template by a script so the mark, wordmark and tagline cannot drift from the product. The apple-touch-icon referenced in index.html was a 404 until now. Icons are drawn on an opaque plate with inset because iOS rounds corners and Android may apply a circle — an edge-to-edge mark loses its ears to that crop. og:image is absolute, which is the single most common reason a card unfurls blank. PIG_INVITE_CODE was a lie. Signup validates against the invites table, so setting the variable only flipped a label in the UI — an operator would set it, hand the code to a colleague, and watch them be rejected. It is now reconciled into a real invite row at boot: setting it issues, changing it rotates and revokes the predecessor, and removing it revokes. Verified all three, plus that a restart with an unchanged code does not duplicate. Two bugs found while doing that: - `uses_remaining` was jsonb, so the SQL decrement could never have worked. Now integer. The generated migration failed because Postgres has no implicit jsonb->integer cast, so the USING clause is hand-written. - Redemption keyed off `redeemedAt`, which would have made every reusable invite single-use — a confusing way to lock a team out. Availability now comes from `usesRemaining`, and redemption records who used it most recently without consuming it. Sign-in gains a password option alongside the magic link, defaulting to password since that is the daily path. PIG stores neither; both are handled by the identity provider and PIG only ever sees the resulting token. autocomplete is set so password managers and iOS can fill. Verified: full migration chain applies to a fresh Postgres, the CSP hash for the inline theme script is unchanged by the rebuild. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
de33a03524 |
Add the web app, seed data, and user-selectable theming
apps/web — React, Vite, Tailwind, shadcn-idiom components. Mobile Safari is a first-class target, not an afterthought: - Two navigation treatments rather than one compromise. A bottom tab bar on phones, because the top of a large phone is out of thumb reach; a persistent sidebar from lg upward, so an iPad in portrait gets it too. - Safe-area insets throughout, so the tab bar clears the home indicator and the last row of a list is actually reachable. - Inputs are pinned to a 16px minimum, which is the correct fix for Safari zooming on focus. user-scalable=no is not used: it breaks pinch-zoom for everyone and recent iOS ignores it anyway. - The pipeline board becomes a stage picker on phones. An eight-column board scrolling horizontally on a 390px screen is technically responsive and practically useless. Theming: users pick an accent and the whole interface re-tints. Accent values live once, in @pig/core, and are written onto the root element at runtime — there is no CSS copy to drift from the TypeScript. Preferences are stored server-side so they follow a person between laptop and phone, mirrored into localStorage only so the pre-paint script can avoid a white flash. Status colours stay fixed regardless of accent: if "at risk" re-tinted to whatever someone picked, the signal would be gone. Seed data is public research, every record carrying a confidence grade and a source URL. No email addresses are seeded or inferred — none are published, and guessing them from a name and a domain is unreliable and rude. Authorship is not promoted to employment: contributors, residency participants and alumni are recorded as what the evidence actually shows, and a name that could not be sourced at all is listed as unresolved rather than invented. Three defects found and fixed by actually running it rather than assuming: 1. The seed was not idempotent. onConflictDoNothing() with no target is a no-op without a matching unique constraint, so a second run duplicated 27 contacts. There is deliberately no unique index on (account, name) — two people at one company can share a name — so idempotency is enforced in the seed instead of by bending the schema. 2. /capacity scrolled sideways on a phone. Grid items default to min-width:auto and `truncate` sets nowrap, so a long title became unshrinkable content and widened the track. Fixed with min-w-0 on every truncating grid child. 3. The idle-capacity alert silently failed to fire at exactly 80% utilisation, losing a float comparison against a 0.2 threshold. Moved to 0.15, which is also a more sensible line for "worth attention". The worked example is tuned to teach rather than to flatter: 70% sold at a 53% markup lands at +6.7% margin with 20% still idle, so both the healthy number and the alert are visible. Drop the sold share to 55% and the same block goes underwater — that sensitivity is the argument for the product. Verified in a real browser at 393px and 1440px, light and dark: zero horizontal overflow on every route, zero console errors. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |