Files
fourge-portal/layout.md
T
Krao Hasanee 04e0911e9f fix: crash bugs in TeamInvoices + faster load
Bug fixes:
- TeamInvoices: add useAuth import/currentUser (invoice-create crash)
- TeamInvoices: setChartYear -> setExportYear (year-dropdown crash)
- TeamDashboard: drop redundant setState-in-effect (cascading renders)
- Companies: delete dead _UnusedClientCompanies (illegal hook calls)
- annotate intentional empty PDF-fallback catches

Load speed:
- preconnect/dns-prefetch to Supabase origin
- lazy-load heic-to in Converters: page chunk 2737KB -> 9KB
- split recharts into its own 'charts' vendor chunk

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 12:59:11 -04:00

40 KiB
Raw Blame History

Fourge Portal Layout System

This is the single source of truth for dashboard/profile visual structure and UI geometry.

1) Global Frame

  • Viewport app shell: height: 100vh, overflow: hidden
  • Main content gutter: 24px all sides
  • Sidebar: width: 76px, top: 24px, left: 24px, height: calc(100vh - 48px), border-radius: 8px
  • Main wrapper offset from sidebar: margin-left: 100px
  • Page rhythm unit: 24px (header spacing, card gaps, section gaps)

2) Theme + Background

  • Background ownership is body via background: var(--bg).
  • Dark base token --bg is full gradient:
    • radial glow + vertical dark gradient.
  • Light base token --bg is full gradient:
    • gray radial glow + white vertical gradient.
  • A faint ambient grid may sit above the base gradient but behind all app content:
    • use a fixed body::before layer with very low-contrast line color
    • keep the static grid a touch more visible than before, but still quiet
    • any animation should be tiny spark nodes that travel on the grid lines, not wide screen sweeps
    • use several small sparks across the page with varied directions so the motion feels distributed rather than like one falling line
    • avoid additional decorative shell/menu entrance animations so the sparks are the only ambient page motion
    • keep animation slow and drifting, not pulsing or flashy
    • keep the grid masked/faded so it is strongest near the top and softer lower in the page
  • Auth/login screens use the same shared body background and spark effect as the app shell; auth surfaces should stay transparent around the card so the global background remains visible.
  • Do not use html theme-gradient scripting for Safari chrome behavior.

3) Tokens

  • Accent: #F5A523
  • Card bg dark: rgba(255,255,255,0.02)
  • Card bg light: rgba(0,0,0,0.02)
  • Sidebar/menu shell uses the same transparent card background token as cards
  • Secondary card tone dark: rgba(255,255,255,0.08)
  • Secondary card tone light: rgba(0,0,0,0.08)
  • Border dark: rgba(245,165,35,0.15)
  • Border light: rgba(0,0,0,0.1)
  • Text primary dark/light: #ffffff / #0d0d0d
  • Text secondary dark/light: #a8a8a8 / rgba(0,0,0,0.6)
  • Text muted dark/light: #666666 / rgba(0,0,0,0.38)

4) Typography

  • Font family: Fourge, then -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif
  • Base font size: 14px
  • Header title: 28px, 500, line-height: 1.2
  • Header subtitle: 13px
  • Widget title: 11px, 500, uppercase, letter-spacing: 0.8px
  • Section header (new standard): 18px, 500, Title Case, letter-spacing: 0.2px, line-height: 1.1
  • Body table text: 12px/13px by column importance

5) Card System

  • Default widget shell:
    • background: var(--card-bg)
    • border: 1px solid var(--border)
    • border-radius: 8px
    • padding: 18px 21px
    • backdrop-filter: blur(12px) + -webkit-backdrop-filter
  • Card width is not an intrinsic card token.
    • Width is owned by the page/grid/container the card lives in.
    • Cards should fill the slot provided by that page layout unless a page-level rule explicitly fixes a track.
  • Compact card radius (legacy generic .card): 4px (do not use for new dashboard widgets)

6) Header + Top Right Controls

  • Site header: padding-top: 24px, padding-bottom: 24px
  • Right control row:
    • Search icon button: 32x32
    • Search button to theme toggle space: 7px (search-wrap margin-right)
    • Theme toggle: 32x32
    • Theme toggle to avatar: 14px (avatar-wrap margin-left)
    • Avatar button: 49x49, circle, 2px inner ring + 2px accent outline

6.5) Section Control Bars (Tabs + Actions)

  • For page-level card controls (ex: Tasks/Projects, Finances tabs):
    • container uses: display: flex, align-items: center, gap: 4, margin-bottom: 10, flex-shrink: 0
    • margin-bottom: 10 is the site-wide standard gap between any tab/control bar and the card(s) below — use this everywhere, no exceptions
    • tab bar row must always be min-height: var(--btn-height) so the gap to the card never shifts when action buttons appear/disappear
    • tabs stay on the left in source order
    • when a tab has a count, use the same inline badge pattern as task detail tabs:
      • margin-left: 5
      • font-size: 12
      • font-weight: 600
      • background: var(--card-bg-2)
      • border: 1px solid var(--border)
      • border-radius: 10px
      • padding: 3px 8px
      • active tab count uses var(--accent) text; inactive uses var(--text-muted)
    • action buttons group sits on the right using: margin-left: auto, display: flex, align-items: center, gap: 8
    • do not use hardcoded spacer blocks (width filler divs) to force alignment
    • icon-only filter/action buttons share the same row and align vertically with add buttons
    • filter button wrapper: <div style={{ position: 'relative', display: 'flex', alignItems: 'center' }}>display: flex prevents block stretching that misaligns the button vertically
    • filter button: className="btn btn-outline" + style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}, icon only (no label), funnel SVG <path d="M2 4h12M4 8h8M6 12h4" /> at 13×13; dropdown uses site-header-avatar-menu + site-header-avatar-item classes
    • when the control bar uses a multi-column grid (to align with split-card layouts below), add align-items: center to the grid and min-height: var(--btn-height) to every column so row height is stable across all tab states
    • Tasks/Projects page tab rules:
      • default landing tab is New Requests
      • keep All Tasks, In Progress, On Hold, In Review, and Completed
      • replace the old To Do tab with:
        • New Requests for not_started rows on R00
        • Revisions for not_started rows on R01+
      • the project table tabs on the right also use the same count badge pattern

7) Dashboard Grids (Team)

  • Stat row: grid-template-columns: 1fr 1fr 1fr 1.5fr, gap: 24, margin-bottom: 0
  • Row 2: grid-template-columns: 1fr 1fr 280px, gap: 24, margin-top: 24
    • Order: Recent Activity (left), Tasks In Progress (center), Calendar (right, fixed width)
  • Row 3: grid-template-columns: 1fr 1fr, gap: 24, margin-top: 24
  • Row 4 full-width: margin-top: 24

8) Stat Cards

  • Card min height: 120px
  • Internal row gap: 21px
  • Label/value/sub spacing:
    • Label: margin-bottom: 5px
    • Value: 30px, 400, letter-spacing: -0.5, line-height: 1.1
    • Sub: 12px, margin-top: 5px
  • Icon badge: 27x27, circle
  • Icon glyph: 13x13

9) Calendar

  • Card uses widget shell
  • Header-to-grid gap: 14px
  • Weekday label: 10px, 600, letter-spacing: 0.5
  • Day cell button: 28x28, circular
  • Day number: 12px
  • Today style: bg #F5A523, text #0d0d0d, 700
  • Dots: up to 3, each 3x3, gap 2
  • Popover:
    • Anchored left of cell: right: calc(100% + 8px), vertical centered
    • width: 210px, padding: 10px 12px, border-radius: 8px
    • shadow 0 12px 32px rgba(0,0,0,0.45)
    • row dot 6x6, row text 12px

10) Activity + Performance Rows

  • Visible rows target: 5
  • Row layout: display:flex, align-items:center, gap:10px
  • Row spacing: margin-top: 10px from second row onward
  • Name text: 13px
  • Meta/date text: 11px
  • Progress track: height: 4px, radius: 2px
  • Percentage width slot: min-width: 28px
  • Empty states in all cards (dashboard, profile, tasks, projects, etc.):
    • any No ... message is centered in the card body (display:flex, align-items:center, justify-content:center)
    • use shared class treatment (.card-empty-center) for consistency
    • avoid top-offset-only placement for empty text

11) Tables

  • General table layout in dashboard cards: table-layout: fixed, border-collapse: collapse
  • Column widths are table-specific, not universal tokens.
    • Widths belong to the individual table/view that defines them.
    • Reuse widths only when the same table pattern is intentionally repeated.
  • Header cells:
    • font-size: 10px, font-weight: 500, uppercase, letter-spacing: 0.6px
    • bottom spacing: padding-bottom: 12px
    • sticky behavior for scrollable tables:
      • table headers stay fixed while body scrolls
      • use shared sticky-head treatment for all app tables (position: sticky; top: 0)
    • table scrollbars are visually hidden for table scroll containers; wheel/trackpad scrolling remains active
  • Body cells:
    • primary text: 13px
    • secondary/metrics text: 12px
    • row vertical spacing via cell padding: typically 5px
  • Hot Tasks column widths:
    • check 10%, task 40%, requested by 35%, due by 15%
  • Sorting rule:
    • Every visible data column header must be sortable.
    • Use clickable header controls (SortTh) with ascending/descending indicator.
    • Exclude only non-data utility/action columns (checkbox-only, icon-only status marker, action buttons).

12) Profile Page

  • Container: full available content width, column, gap: 24
  • Top row: grid-template-columns: 60fr 40fr, gap: 24
  • At <=1200px: top row stacks to one column
  • Main profile card uses widget shell
  • Profile card width is determined by the profile page grid, not by the card itself.
  • Internal card layout:
    • row gap: 20px
    • portrait max 140x140, circle
    • portrait aligns flush in the row without extra wrapper padding/side space
    • detail grid 140px 1fr, row-gap: 8, column-gap: 12, margin-top: 14
    • profile name/title: 18px, 500, line-height: 1.2
    • company subtitle: 13px, secondary text
    • contact/detail rows: 13px, primary text
    • social row margin-top: 14, gap: 8
    • company line under title is always visible; fallback display is when no company is assigned
    • right-side meta labels (Member Since, Role) use widget-title sizing: 11px, 500, uppercase, letter-spacing: 0.8px
    • right-side meta values use body sizing: 13px
    • self-only edit button: position: absolute, top: 18px, right: 21px (aligns to card padding), border-radius: 8px (matches card), height: 22px, font-size: 12px, padding: 0 12px
    • default outline button hover/focus fills with accent gold and uses dark text
  • Right calendar card shows only tasks/events assigned to the viewed profile user
  • Profile page left column includes a Tasks In Progress card above Recent Activity
  • Profile Tasks In Progress follows the dashboard table/card pattern:
    • same widget shell and header treatment
    • same show-all behavior (5 visible by default)
    • profile-specific filter is tasks assigned to the viewed user with statuses in_progress, on_hold, or client_review
    • columns are Task and Status
  • Profile activity feed action text pattern: Task started, Task submitted, Task approved, Task rejected (sentence title case)
  • Activity feed includes:
    • actions performed by the viewed user
    • Task approved and Task rejected entries for tasks assigned to that viewed user (even if actioned by someone else)

12.25) Shared Portrait Logic

  • Shared profile/avatar rendering must use one common resolver path instead of page-specific inline <img> fallbacks.
  • Canonical shared component: ProfileAvatar
  • Portrait source resolution order:
    • explicit avatarUrl prop when already resolved
    • profile.avatar_url
    • profile lookup by profileId
    • profile lookup by normalized display name as a fallback when only sender/actor name exists in derived dashboard data
    • initials fallback when no profile image is available
  • Shared portrait behavior:
    • image uses object-fit: cover
    • shape is always circular (border-radius: 50%)
    • fallback initials use accent background with dark text
    • layout/header/task/project/dashboard surfaces should reuse the shared portrait resolver instead of hand-rolling image + initials logic

12.3) Team Dashboard Data Logic

  • Team dashboard is shared across roles, but card data is role-filtered by scoped tasks/projects/companies.
  • Focus refresh + realtime refresh should be wired through the shared live-refresh path, not duplicated per page.
  • Scoped company/project/task IDs should be resolved through the shared scope resolver for client/external views.
  • Tall dashboard list cards should use viewport-driven height with internal scroll instead of hard-coded row counts:
    • measure the lower dashboard row against the current window height and size the cards to the remaining viewport space
    • do not use a generic fixed tall-card height that can force whole-page scrolling on shorter windows
    • do not apply a lower clamp that stops the card from continuing to compress with the window
    • scrolling stays inside the card body, not on the full page layout
  • Team Performance card logic:
    • source data is deliveries joined to submissions and tasks
    • each delivery row must include submission.task_id plus nested task assignee info
    • non-team roles must filter deliveries back down to their scoped task IDs before calculating performance rows
    • month dropdown options are derived only from months that actually contain scoped delivery data
    • initial render must use the first valid month with data; card must not render empty until the dropdown is manually changed
    • performer identity resolves first from task.assigned_to, then falls back to normalized sent_by / display name matching against profiles
    • avatar in Team Performance follows shared portrait resolution rules above
    • counts:
      • version_number === 0 => new
      • version_number > 0 => revision
    • progress bar percentage is performer completed count divided by total completed count in the active month
  • Dashboard lower-left task card logic:
    • team view remains Hot Tasks
    • client view remains Tasks Ready For Review
    • external/subcontractor view is My Tasks
    • subcontractor My Tasks must source directly from scoped tasks assigned to the current subcontractor, not from is_hot submission flags
    • subcontractor My Tasks should include only active assigned tasks:
      • not_started
      • in_progress
      • client_review
      • on_hold
    • subcontractor My Tasks should exclude completed/closed states such as:
      • client_approved
      • invoiced
      • paid
    • subcontractor My Tasks rows should show task, project, status, and due date

13) Radius + Geometry Rules

  • Dashboard/profile widgets: 8px radius
  • Sidebar: 8px radius
  • Standard app buttons use the Edit Profile geometry:
    • geometry is tokenized globally in CSS vars and must be reused (no per-page hardcoded button geometry):
      • --btn-height
      • --btn-padding-x
      • --btn-radius
      • --btn-font-size
      • --btn-font-weight
      • --btn-letter-spacing
      • --btn-line-height
    • height: 22px
    • horizontal padding: 0 12px
    • border-radius: 8px
    • font-size: 11px
    • font-weight: 500
    • letter-spacing: 0.8px
    • text-transform: uppercase
    • line-height: 1
    • label color follows theme text roles:
      • dark mode: primary button labels use dark text on accent fill; outline labels use primary text on transparent surface
      • light mode: outline labels use light-theme primary text; danger labels use danger text
    • outline button border uses the same var(--border) token as card outlines unless a button role explicitly overrides it
    • keep existing button role styling intact (outline, primary, danger); geometry changes, not role colors
    • popup/modal footer action rows are a no-wrap horizontal row:
      • display: flex
      • justify-content: flex-end
      • gap: 8px
      • flex-wrap: nowrap
    • popup/modal action buttons use a stable minimum width so loading labels do not shove adjacent buttons underneath:
      • min-width: 108px
      • center content horizontally
    • request/add-task popups, review/revision/reject/amend popups, and other form submit dialogs should keep the save/submit/cancel buttons on one line while the submit label changes to Saving... / Submitting...
    • status action buttons that change the task state must update the page immediately and lock while the save is in flight so users cannot double-press:
      • flip local UI state optimistically
      • disable related status buttons until request finishes
      • if save fails, roll back local state and show error
  • route/page loading should continue using the shared PageLoader until page data is actually ready
  • avoid plain page-level Loading... text fallbacks after navigation, or the popup can disappear while the route is still fetching
    • pages with longer initial fetches should pass their route loading state into shared layout/page shells so the popup stays up until all first-load data for that screen is ready
  • Inputs/dropdowns may use their own control radius unless a page-specific rule says otherwise
  • Circular elements (avatar/day/icon badges): 50%

14) Z-Index Stack

  • Sidebar: 200
  • Header dropdowns/tooltips: 300
  • Calendar hover popover: 1002 within card context (card can be 1001 active)
  • Modal overlay: 1200

14.5) Loading Popup

  • Use shared PageLoader component for loading overlays instead of page-specific popup implementations.
  • Loading popups/overlays, including shared site-wide page loaders, use the same widget shell as dashboard cards:
    • background: var(--popup-bg)
    • border: 1px solid var(--border)
    • border-radius: 8px
    • padding: 18px 21px
    • backdrop-filter: blur(12px) + -webkit-backdrop-filter
  • Popup surface is theme-aware:
    • dark mode: deep translucent dark surface
    • light mode: bright translucent white surface
  • Loading overlay scrim is theme-aware:
    • dark mode: rgba(0,0,0,0.58)
    • light mode: soft light scrim via --overlay-scrim
  • Loading title uses widget header treatment: 11px, 500, uppercase, letter-spacing: 0.8px, secondary text
  • Progress track:
    • height: 4px
    • radius: 2px
    • track color: secondary card tone (var(--card-bg-2))
    • progress fill: accent
  • Progress/meta text under the bar uses subtext sizing (12px) and secondary text

14.6) Modal Form Fields

  • Modal forms that follow Edit Profile styling (including Add Task/New Task) share the same field typography and theme tokens:
    • modal surface transparency matches Edit Profile card shell (background: var(--card-bg)), not a denser popup-specific override
    • field labels: 11px, 500, uppercase, letter-spacing: 0.8px, color var(--text-secondary)
    • input/select text: 13px, color var(--text-primary), left aligned
    • textarea text: 13px, color var(--text-primary)
    • field surfaces and borders use global theme tokens (var(--card-bg-2) + var(--border)) for dark/light parity
    • modal form action buttons use the standard outline geometry/style (btn btn-outline) unless a page explicitly defines a different role
  • Finance modal numeric fields (expense amount, invoice money inputs, line-item quantity/rate fields) follow the same shell system instead of custom oversized styles:
    • amount/currency field outer shell: min-height: 42px, border-radius: 6px, padding: 0 12px, background: var(--card-bg-2), border: 1px solid var(--border)
    • currency symbol is separate from the numeric input and uses secondary text
    • amount input text: 22px, 500, line-height: 1.1, right aligned, font-variant-numeric: tabular-nums
    • line-item numeric inputs keep the normal field feel: min-height: 32px, 13px text
    • quantity is centered; rates/prices/totals are right aligned with tabular numerals
    • do not use giant red standalone number inputs; red is reserved for negative/danger/read-only emphasis, not default editable money entry

14.7) Status Tags

  • Shared status tags use StatusBadge (.badge.badge-status).
  • Rule: any workflow/state value (not_started, in_progress, on_hold, client_review, client_approved, invoiced, paid, active, completed, etc.) must render through StatusBadge only.
  • Do not use raw span.badge badge-* for statuses.
  • Base badge geometry:
    • display: inline-flex
    • align-items: center
    • gap: 4px
    • padding: 3px 10px
    • border-radius: 4px
    • font-size: 11px
    • font-weight: 400
    • letter-spacing: 0.3px
    • white-space: nowrap
  • Status badge geometry override (.badge-status):
    • display: inline-flex
    • align-items: center
    • justify-content: center
    • min-width: 78px
    • height: 20px
    • padding: 0 8px
    • font-size: 10px
    • line-height: 1.05
    • box-sizing: border-box
    • padding-top: 1px (optical vertical-centering correction for current font metrics)
  • Status colors are variant classes (.badge-not_started, .badge-in_progress, etc.) and are theme-aware.
  • Non-status chips (example: invoice line-item type Initial/Revision, or service-type labels) may use .badge variants directly.
  • When a non-status chip should visually align with status pills, add .badge-status to match geometry.
  • Exception: compact urgent tag (.badge-needs_revision) uses tighter geometry:
    • min-width: 28px
    • padding: 3px 4px
    • border-radius: 4px
    • fixed red treatment in both dark/light themes.

15) Motion

  • Motion vars:
    • fast 160ms
    • base 220ms
    • easing cubic-bezier(0.22, 1, 0.36, 1)
  • Dropdown/menu entrance animation should be minimal; avoid decorative motion when it competes with the shared background spark effect.

17) Hover Interaction Contract

  • Sidebar, header icon buttons, dropdown items, and avatar menu items must show a visible hover surface before click.
  • Single hover source-of-truth block controls these elements.
  • Dark hover surface baseline: #1f1f1f.
  • Light hover surface baseline: rgba(0,0,0,0.08).
  • Nav icon opacity must lift from muted to full on hover (opacity: 1).
  • Clickable text links use text-only highlight on hover/focus (color: var(--accent)), not whole-row/background fill.
  • Hover and active must be visually distinct:
    • hover uses stronger temporary contrast (bg + thin border),
    • active remains persistent selected-state background.
  • Use shared link classes only; do not hand-roll page-specific link hover styles:
    • table/text links inside cards and tables: table-link
    • inline action links/buttons in cards/feeds: dashboard-inline-link
  • Hover behavior for both classes:
    • color changes to accent gold (var(--accent))
    • no underline on hover/focus
    • cursor remains pointer
  • For table-heavy rows where hit targets are tight, row hover may also promote link color to accent; still keep text-only link treatment (no full-row fill just for links).
  • Do not rely on inline style color overrides for hover behavior; if a variant is needed, add/extend a shared class in index.css.

16) Non-Negotiable Implementation Rules

  • Keep gradient backgrounds on body, not html
  • Keep widget shell values (18px 21px, 8px, blur 12, border token) consistent
  • Maintain global 24px spacing rhythm for page/frame/grid gaps
  • Keep team dashboard card order:
    1. Open Tasks
    2. Active Projects
    3. Net Profit
    4. Revenue (wide)
  • Keep row 2 order: Recent Activity (left), Tasks In Progress (center), Calendar (right fixed at 280px)
  • Keep the lower dashboard row shared across all three roles:
    1. role-specific task list card
    2. Team Performance
  • Do not add an extra team-only Client Highlight card under the shared dashboard rows

17) Team Reports

  • Team-only tools nav includes Reports.
  • Reports page is a finance-adjacent audit surface, but separate from the invoice page.
  • Primary report source is task-version billing units, not lump tasks:
    • R00 is its own row
    • each real revision version (R01, R02, and so on) is its own row
  • Report rows must derive from the same version-unit billing logic as invoice creation:
    • initial/new rows map to task-level client invoice units
    • revision rows map to submission-level client invoice units
    • hidden review-shadow submissions must not create report rows
  • Minimum columns:
    • company
    • project
    • task name
    • R##
    • type (New / Revision)
    • version workflow status
    • billing status tag (Not Invoiced, Invoiced, Paid)
  • Reports page must support filtering by:
    • company
    • project
  • Reports page must support PDF export of the currently filtered rows.

18) Invoice Rules

  • New work (R00) is billed at the new-service rate.
  • Revision R01 is free.
  • Revisions R02+ are billed incrementally: each uninvoiced revision entry contributes exactly 1x revision charge.
  • Previously invoiced revisions are excluded; later invoices only charge newly uninvoiced revisions (e.g. if R02 was already invoiced, at R04 only R03 + R04 are billed).
  • Billing is version-unit based, not task-lump based:
    • R00 is one unit
    • R01 is one unit
    • R02+ are separate units
  • Team -> client invoice availability:
    • a task/version can appear only if it is client approved
    • it must not have been invoiced to the client before
    • already invoiced or paid client invoice units must not appear again
    • revision availability must be sourced per version submission, not blocked just because the task's R00 was already invoiced
    • invoice add-item UI should show a visible type chip for each unit:
      • New for R00
      • Revision for R01+
    • version availability is tied to completed version state, not only the task's current active state:
      • if a newer version is now active (not_started, in_progress, etc.), older completed uninvoiced versions must still appear
      • example: if R02 is now active, uninvoiced R01 may still appear while R02 must stay off the invoice list until it is completed/approved
    • team invoice popup and standalone team invoice page must both use the same shared version-eligibility source, not separate page-local rules
    • clicking a team invoice from the /finances invoice list should open the same shared invoice-detail popup content used by the standalone team invoice detail route, not a separate simplified modal
    • creating a new team invoice should also open that same shared invoice-detail popup after save
  • Subcontractor -> Fourge invoice availability:
    • a task/version can appear only if that subcontractor actually worked/delivered that version
    • it must be client approved
    • it must not have been invoiced by that subcontractor to Fourge before
    • already invoiced or paid subcontractor invoice units must not appear again
  • Client invoice history and subcontractor invoice history are separate streams:
    • the same task/version may exist in client billing and subcontractor billing
    • duplicate prevention happens within each stream separately

18.0) Request And Task Lifecycle

  • A task starts as a request created by team or client.
  • The initial request is version R00.
  • R00 request data lives on the initial submission row.
  • Visible R## labels on task lists/cards/detail must use the task's true current version:
    • derive from max(tasks.current_version, submission.version_number)
    • do not derive the visible R## from the deadline-bearing submission row
    • deadline/request metadata may come from the deadline source submission, but the displayed revision number must still reflect the true current version
  • Client and team can amend the current request without creating a new visible revision item:
    • amendment stays tied to the same active version
    • amendment does not increment R##
  • Team or subcontractor works the task and places it in review.
  • Review submissions belong in the Submissions tab and may happen multiple times for the same R##.
  • Client decision on a review:
    • Approve:
      • task moves to client_approved
      • task remains on the approved version
    • Reject:
      • task returns to in_progress
      • same assigned worker stays on the task
      • R## does not increment by rejection alone
      • rejected reason is stored as Rejected Notes tied to that same version
  • A real revision request creates the next visible version:
    • R00 -> R01
    • R01 -> R02
    • etc.
    • when a revision request creates the next R##, the task keeps the same last assignee instead of clearing assignment
    • status resets to not_started, but the assignee remains attached so the work comes back to the same person by default
  • Revision requests are separate from rejection notes:
    • revision request = new visible request/version item
    • rejection note = side note on the current version
  • Re-review after rejection must still appear in Submissions without creating a fake new visible request/revision row.

18.1) Task File Placement Rules

  • Company and project folder chain:
    • when a new company is created by team, the portal ensures a matching server folder exists:
      • /Clients/{Company}
    • when a company name is changed, the company folder is renamed to match
    • when a new subcontractor is created by team, the portal ensures a matching server folder exists:
      • {FILEBROWSER_SUBS_ROOT}/{Subcontractor Name}
    • when a subcontractor name is changed, that subcontractor folder is renamed to match
    • when a new project is created, the portal ensures a matching server folder exists:
      • /Clients/{Company}/Projects/{Project}
      • inside each new project folder, the portal also pre-creates:
        • 00 Project Files
    • when a project name is changed, the project folder is renamed to match
    • when a new task request is created, the portal ensures a matching server folder exists:
      • /Clients/{Company}/Projects/{Project}/{Task}
      • inside each new task folder, the portal also pre-creates:
        • Old Books
        • Working Files
        • Survey
    • folder creation now works via FileBrowser API using:
      • a valid production FILEBROWSER_TOKEN
      • FileBrowser source srv
    • folder sync uses FileBrowser only for server folder creation
    • this does not restore the old file-sharing page or task folder tab
  • Initial task request files:
    • created by team or client
    • attach to the initial request in the Overview tab
    • stored on the initial submission
    • file records live in submission_files
    • request file flow now works in this order:
      • ensure the matching task folder exists under /Clients/{Company}/Projects/{Project}/{Task}
      • upload each attached file into Supabase bucket submissions
      • store the file row in submission_files
      • then mirror that same saved file from Supabase into the server-side Survey folder
    • initial request files are stored in Supabase under the task's Survey path for portal access
      • storage path format:
        • {taskId}/Survey/{timestamp}_{originalFileName}
    • initial request files are also mirrored into the server-side task folder:
      • /Clients/{Company}/Projects/{Project}/{Task}/Survey
    • the mirror now copies from the saved Supabase storage object on the server side instead of re-uploading the browser file body
    • multiple attached request files are processed one by one through that same mirror flow and should all land in the same Survey folder
    • the live working behavior is:
      • request attachments still show inside the portal from Supabase
      • the same attachments also appear in the matching server Survey folder
    • for Brand Book requests, sign details are stored directly on the submission row
    • sign detail payload lives in submissions.signs
    • old submission_signs rows are migrated into submissions.signs and no longer used by the portal
    • sign info in the Overview tab renders as plain text rows with no inset background card
  • Revision request files:
    • created by team or client
    • attach to the specific revision entry in the Revisions tab
    • stored on the revision submission
    • file records live in submission_files
  • Review submission files:
    • created by team or subcontractor
    • attach to the specific submitted review entry in the Submissions tab
    • stored on the delivery
    • file records live in delivery_files
  • Client rejection notes:
    • when client rejects a task in review, it must not create a new request/revision item
    • rejection opens a note box
    • rejection returns the task to in_progress
    • rejection keeps the same assigned worker on the task
    • rejection does not increment the R## version by itself
    • note is tied to the exact version being rejected
    • R00 rejection note shows beside the R00 request info
    • R01 rejection note shows beside the R01 request info
    • R02 rejection note shows beside the R02 request info
    • label should read Rejected Notes
    • these notes are separate from normal comments and should not inflate the visible comments count

18.2) Task Notification Rules

  • New request:
    • when a new request is created, it emails:
      • all team members
    • this applies from client request flows and team request flows
  • In review:
    • when a task is placed in review, it emails:
      • the client
      • all team members
      • the assigned user
  • On hold:
    • when a task is placed on hold, it emails:
      • the client
      • all team members
      • the assigned user
  • Amend request:
    • when a request is amended, it emails:
      • the client
      • all team members
      • the assigned user
  • Rejected:
    • when a client rejects a task, it emails:
      • the client
      • all team members
      • the assigned user
    • rejected task email uses the shared task-status layout only
    • do not also send a separate legacy revision email
  • Approved:
    • when a client approves a task, it emails:
      • the client
      • all team members
      • the assigned user
  • Revision request:
    • when a revision request is created, it emails:
      • the client
      • all team members
      • the assigned user
    • revision request should send one shared task-status email only
    • do not also send any older legacy revision email

19) Finance Flow

  • Finance is role-split, not one shared page across all users:
    • team finance hub: /finances
    • client invoice view: /client-invoices
    • subcontractor invoice view: /subs-invoices
    • subcontractor PO view: /my-purchase-orders
  • Finance is shared in concept, but not one universal page component yet:
    • team sees the full finance hub
    • client sees receive/view/pay invoice flow only
    • subcontractor sees invoice-Fourge and PO-review flow only

19.1) Team -> Client Invoice Flow

  • Team creates invoice for the client.
  • Team invoice availability is version-unit based:
    • R00 can bill as new work
    • R01 is tracked but free
    • R02+ can bill as revision units
  • A client invoice unit may appear only if:
    • it is client approved
    • it has not already been invoiced to the client
  • Team sends invoice to the client.
  • Invoice send email goes to:
    • the client recipient
    • all team members
  • Client does not create invoices back to Fourge in the portal.
  • Client only receives, views, pays, and downloads invoice records.
  • Client payment can happen:
    • through Stripe / portal pay flow
    • outside the portal, then team marks the invoice paid manually
  • Once paid:
    • invoice status is paid
    • branded paid/receipt email goes to:
      • the client recipient
      • all team members
    • receipt can be generated/downloaded/sent if needed
    • paid client invoices must not be deletable by client or team
  • Team invoice detail remains the control point for:
    • send / resend invoice
    • mark paid
    • generate invoice PDF
    • generate/send receipt

19.2) Client Finance Role

  • Clients only see invoices relevant to their company access.
  • Clients can:
    • view invoices
    • filter invoices by company when tied to multiple companies
    • download invoice PDFs
  • Clients cannot:
    • create invoices to Fourge
    • manage expenses
    • manage subcontractor invoices
    • manage POs

19.3) Subcontractor -> Fourge Invoice Flow

  • Subcontractor creates invoice to bill Fourge for completed work.
  • Subcontractor billing is version-based, not task-lump based:
    • R00 is one billable unit
    • R01 is one version unit but free
    • R02+ are version units and billable
  • Subcontractor invoice create page builds available rows from actual delivered versions tied to that subcontractor's assigned tasks.
  • Only versions the subcontractor actually delivered should appear as available invoice rows.
  • Subcontractor invoice availability must dedupe per task + version.
  • If a subcontractor invoice already includes a specific version, that same version must not show up again in later subcontractor invoices.
  • A subcontractor task/version is available only when:
    • it is client approved
    • that subcontractor delivered/worked that version
    • it has not already been invoiced to Fourge by that subcontractor
  • Version availability follows the same older-completed-version rule as team client invoices:
    • if a newer version is now active (not_started, in_progress, etc.), older completed uninvoiced versions must still appear
    • example: if R02 is now active, uninvoiced R01 may still appear while R02 must stay off the subcontractor invoice list until it is completed/approved
    • subcontractor invoice create page must use that same shared version-eligibility source as the team invoice flows
  • That invoice appears to team finance for review/payment.
  • Team marks subcontractor invoices paid.
  • Subcontractors do not normally mark their own invoices paid.
  • Once paid:
    • subcontractor sees paid status
    • branded paid/receipt email goes to:
      • that subcontractor
      • all team members
    • receipt can be generated/downloaded/sent if needed
  • Client invoicing and subcontractor invoicing are separate streams:
    • team can invoice the client for a task/version
    • subcontractor can invoice Fourge for that same task/version
  • Client invoice state must not block subcontractor invoice availability by itself.
  • Subcontractor invoice state only blocks the same subcontractor task/version from being billed again.
  • Subcontractor finance page /subs-invoices stays separate from the team finance hub:
    • no chart on this page
    • top row uses four stat cards only: completed new tasks, completed revisions, invoiced, paid
    • invoice history sits below in one full-width card with no secondary right-side companion card
    • the + Invoice action uses the same shared popup shell/button treatment as the team finance invoice modal
    • subcontractor invoice modal keeps subcontractor-specific fields and billing rules, but visually follows the shared finance popup pattern
    • clicking a subcontractor invoice from the list should open the shared invoice detail in a popup, matching the team-side subcontractor invoice interaction
    • after a subcontractor creates an invoice, the portal should open that same popup detail view instead of routing away to a separate detail page
    • subcontractor invoice popup layout should match the team-side subcontractor invoice popup layout
    • subcontractor invoice popup action row should include:
      • Download Invoice
      • Submit to Team when still draft
      • Download Receipt when paid
    • subcontractor invoice popup line-item table should include:
      • Work (New or Revision)
      • R# (R00, R01, and so on)
      • Description
      • Type (task service type such as Brand Book, Sign Family, and so on)
      • Qty
      • Unit Price
      • Amount
    • subcontractor invoice detail uses one shared layout across team and subcontractor roles, with role-specific actions loaded into the same detail shell
    • shared subcontractor invoice detail pattern:
      • uses the shared site header chevron/back pattern from task detail, not a separate page-local header block
      • content starts immediately under that shared header using the same task-detail rhythm, with no separate action bar floating above the first card row
      • header title styling follows the global site header spec:
        • 28px
        • 500
        • var(--text-primary)
        • line-height: 1.2
      • header subtitle styling follows the global site header spec:
        • 13px
        • var(--text-muted)
      • two top summary cards are the first content row, using the same 24px gap rhythm as task detail
      • role-specific invoice actions sit inside the top-right summary card header instead of on a separate row
      • summary and section labels use widget-title styling:
        • 11px
        • 500
        • uppercase
        • letter-spacing: 0.8px
        • var(--text-secondary)
      • line items live in one full-width card with sticky table header and total footer
      • notes card sits below line items when notes exist

19.4) Team -> Subcontractor PO Flow

  • Team creates purchase order for subcontractor work.
  • PO can optionally be tied to a project and task-backed line items.
  • PO email sending is currently disabled.
  • Subcontractor can approve the PO from their portal view.
  • PO approval is separate from subcontractor invoice payment.
  • A PO being approved does not itself mean an invoice has been paid.

19.5) Simple Mental Model

  • Fourge -> Client = invoice out
  • Client -> Fourge = payment in
  • Subcontractor -> Fourge = invoice in
  • Fourge -> Subcontractor = payment out
  • Fourge -> Subcontractor before payment = PO / work authorization