96e90561b9
- Expense detail popup: 80vw×80vh, inline editing, save gated on dirty state, Download Receipt in footer - Add expense form: same layout with FileAttachment drag-drop on right - New invoice popup: 80vw×80vh inline form replacing navigate-away page - Tasks table: suppress row hover background, only Name/Project table-links highlight - TaskDetail Submissions tab: reads from deliveries table, shows R## version, sent_by, message - Submit for Review: writes to deliveries + delivery_files (deliveries bucket) - deliveries.version_number column added and backfilled from revision history - Port 4173 for dev server Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.5 KiB
4.5 KiB
Fourge Portal — Claude Instructions
Project
React + Vite SPA. Supabase backend. Deployed on Vercel. Local dev on port 5173 (or next available). Remote: gitea remote at gitea.krao.us.
Dev workflow
- Dev server:
npm run dev(Vite, HMR active — no restart needed for code changes) - Deploy:
vercel --prodafter every change, no need to ask - Push:
git push gitea main
Design system — follow layout.md as single source of truth
Card shell (all new widgets)
background: var(--card-bg)
border: 1px solid var(--border)
border-radius: 8px
padding: 18px 21px
backdrop-filter: blur(12px)
-webkit-backdrop-filter: blur(12px)
Tokens
- Accent:
#F5A523 - Card bg:
var(--card-bg)=rgba(255,255,255,0.02)dark /rgba(0,0,0,0.02)light - Secondary card:
var(--card-bg-2) - Border:
var(--border) - Text:
var(--text-primary),var(--text-secondary),var(--text-muted)
Widget title (card section headers)
font-size: 11px
font-weight: 500
text-transform: uppercase
letter-spacing: 0.8px
color: var(--text-secondary)
margin-bottom: 14px
Tables
table-layout: fixed,border-collapse: collapse- Headers:
font-size: 10px,font-weight: 500, uppercase,letter-spacing: 0.6px,padding-bottom: 12px,border: none,background: transparent - Use
SortThcomponent for all sortable columns — every data column must be sortable - Body cells: primary
13px, secondary12px,padding: 5px 0,border: none,background: transparent - Scrollable tables: container gets
className="scrollbar-thin-theme table-scroll-hidden", table getsclassName="table-sticky-head" - Do NOT add
background: transparentinline on TD — it blocks thetr:hover tdCSS rule
Row hover
- Standard table rows:
tr:hover td { background: rgba(255,255,255,0.02) }via global CSS — do not add inline bg to TD - Clickable text inside rows: use
className="table-link"— turns gold on hover, no full-row fill - Do NOT use whole-row click + cursor pointer as the primary interaction pattern for new tables
Section control bar (tabs + actions)
display: flex, align-items: center, gap: 4, margin-bottom: 10, flex-shrink: 0
- Tabs on the left
- Action buttons on the right via
margin-left: auto— do NOT use hardcoded spacer divs for alignment - Reference: Tasks.jsx tabs +
+ Taskbutton pattern
Buttons
- All buttons use
.btnclass with CSS vars (--btn-height: 22px,--btn-radius: 8px, etc.) - Do not hardcode button geometry inline — use
btn btn-outline,btn btn-primary,btn btn-danger - Height controlled by
--h-control=--btn-height
Modal forms
- Surface:
background: var(--card-bg), usepopupSurfaceStyle+popupOverlayStylefrom../../lib/popupStyles - Field labels:
11px,500, uppercase,letter-spacing: 0.8px,color: var(--text-secondary) - Inputs:
font-size: 13px,background: var(--card-bg-2),border: 1px solid var(--border),border-radius: 6px,padding: 6px 10px - Form gap:
14pxbetween fields - Action buttons: right-aligned,
btn btn-outline, gap8px - Header: single
11pxuppercase secondary label — no large heading
Status badges
- Use
<StatusBadge status={...} />— do not hand-roll status tags - Badge height:
20px,border-radius: 4px,font-size: 10px
Spacing rhythm
- Page/grid/section gaps:
24px - Tabs-to-card gap:
margin-bottom: 10on tabs bar
Height-filling layouts
For pages where cards should fill the window:
<Layout>
<div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
{/* fixed sections: flexShrink: 0 */}
{/* filling section: flex: 1, minHeight: 0 */}
</div>
</Layout>
Cards that scroll internally: display: flex, flexDirection: column, minHeight: 0 + inner overflowY: auto, flex: 1, minHeight: 0
File browser paths (per role)
team:/Clients/{Co}/Projects/{Proj}/{Task}(real path)client:/{Co}/Projects/{Proj}/{Task}(virtual — no/Clients/prefix)external:/Projects/{Proj}/{Task}(virtual)
Empty states
Use .card-empty-center class for all "No …" messages inside cards.
Key files
layout.md— visual spec, always check before building new UIsrc/index.css— global tokens and shared classessrc/components/SortTh.jsx— sortable table headersrc/components/StatusBadge.jsx— status tagssrc/lib/popupStyles.js— modal overlay/surface stylesapi/filebrowser.js— file browser API (Vercel serverless)supabase/functions/fbq-proxy/— Supabase edge function for FileBrowser Quantum