04e0911e9f
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>
40 KiB
40 KiB
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:
24pxall 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
bodyviabackground: var(--bg). - Dark base token
--bgis full gradient:- radial glow + vertical dark gradient.
- Light base token
--bgis 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::beforelayer 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
- use a fixed
- Auth/login screens use the same shared
bodybackground and spark effect as the app shell; auth surfaces should stay transparent around the card so the global background remains visible. - Do not use
htmltheme-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/13pxby column importance
5) Card System
- Default widget shell:
background: var(--card-bg)border: 1px solid var(--border)border-radius: 8pxpadding: 18px 21pxbackdrop-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,2pxinner ring +2pxaccent outline
- Search icon button:
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: 10is 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: 5font-size: 12font-weight: 600background: var(--card-bg-2)border: 1px solid var(--border)border-radius: 10pxpadding: 3px 8px- active tab count uses
var(--accent)text; inactive usesvar(--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 (
widthfiller 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: flexprevents 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" />at13×13; dropdown usessite-header-avatar-menu+site-header-avatar-itemclasses - when the control bar uses a multi-column grid (to align with split-card layouts below), add
align-items: centerto the grid andmin-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, andCompleted - replace the old
To Dotab with:New Requestsfornot_startedrows onR00Revisionsfornot_startedrows onR01+
- the project table tabs on the right also use the same count badge pattern
- default landing tab is
- container uses:
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
- Label:
- 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, gap2 - 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 text12px
- Anchored left of cell:
10) Activity + Performance Rows
- Visible rows target: 5
- Row layout:
display:flex,align-items:center,gap:10px - Row spacing:
margin-top: 10pxfrom 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
- any
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
- primary text:
- Hot Tasks column widths:
- check
10%, task40%, requested by35%, due by15%
- check
- 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
- row
- Right calendar card shows only tasks/events assigned to the viewed profile user
- Profile page left column includes a
Tasks In Progresscard aboveRecent Activity - Profile
Tasks In Progressfollows 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, orclient_review - columns are
TaskandStatus
- 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 approvedandTask rejectedentries 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
avatarUrlprop when already resolved profile.avatar_url- profile lookup by
profileId - profile lookup by normalized display
nameas a fallback when only sender/actor name exists in derived dashboard data - initials fallback when no profile image is available
- explicit
- 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
- image uses
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 Performancecard logic:- source data is
deliveriesjoined tosubmissionsandtasks - each delivery row must include
submission.task_idplus 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 normalizedsent_by/ display name matching against profiles - avatar in
Team Performancefollows shared portrait resolution rules above - counts:
version_number === 0=>newversion_number > 0=>revision
- progress bar percentage is performer completed count divided by total completed count in the active month
- source data is
- 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 Tasksmust source directly from scoped tasks assigned to the current subcontractor, not fromis_hotsubmission flags - subcontractor
My Tasksshould include only active assigned tasks:not_startedin_progressclient_reviewon_hold
- subcontractor
My Tasksshould exclude completed/closed states such as:client_approvedinvoicedpaid
- subcontractor
My Tasksrows should show task, project, status, and due date
- team view remains
13) Radius + Geometry Rules
- Dashboard/profile widgets:
8pxradius - Sidebar:
8pxradius - Standard app buttons use the
Edit Profilegeometry:- 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: flexjustify-content: flex-endgap: 8pxflex-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
- geometry is tokenized globally in CSS vars and must be reused (no per-page hardcoded button geometry):
- route/page loading should continue using the shared
PageLoaderuntil 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:
1002within card context (card can be 1001 active) - Modal overlay:
1200
14.5) Loading Popup
- Use shared
PageLoadercomponent 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: 8pxpadding: 18px 21pxbackdrop-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
- dark mode:
- 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
- height:
- Progress/meta text under the bar uses subtext sizing (
12px) and secondary text
14.6) Modal Form Fields
- Modal forms that follow
Edit Profilestyling (includingAdd Task/New Task) share the same field typography and theme tokens:- modal surface transparency matches
Edit Profilecard shell (background: var(--card-bg)), not a denser popup-specific override - field labels:
11px,500, uppercase,letter-spacing: 0.8px, colorvar(--text-secondary) - input/select text:
13px, colorvar(--text-primary), left aligned - textarea text:
13px, colorvar(--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
- modal surface transparency matches
- 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,13pxtext - 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
- amount/currency field outer shell:
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 throughStatusBadgeonly. - Do not use raw
span.badge badge-*for statuses. - Base badge geometry:
display: inline-flexalign-items: centergap: 4pxpadding: 3px 10pxborder-radius: 4pxfont-size: 11pxfont-weight: 400letter-spacing: 0.3pxwhite-space: nowrap
- Status badge geometry override (
.badge-status):display: inline-flexalign-items: centerjustify-content: centermin-width: 78pxheight: 20pxpadding: 0 8pxfont-size: 10pxline-height: 1.05box-sizing: border-boxpadding-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.badgevariants directly. - When a non-status chip should visually align with status pills, add
.badge-statusto match geometry. - Exception: compact urgent tag (
.badge-needs_revision) uses tighter geometry:min-width: 28pxpadding: 3px 4pxborder-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)
- fast
- 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.
- hover uses stronger temporary contrast (
17.5) Link Interaction Standard
- 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
- table/text links inside cards and tables:
- Hover behavior for both classes:
- color changes to accent gold (
var(--accent)) - no underline on hover/focus
- cursor remains pointer
- color changes to accent gold (
- 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, nothtml - Keep widget shell values (
18px 21px,8px, blur 12, border token) consistent - Maintain global
24pxspacing rhythm for page/frame/grid gaps - Keep team dashboard card order:
- Open Tasks
- Active Projects
- Net Profit
- 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:
- role-specific task list card
- Team Performance
- Do not add an extra team-only
Client Highlightcard 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:
R00is 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
R01is free. - Revisions
R02+are billed incrementally: each uninvoiced revision entry contributes exactly1xrevision charge. - Previously invoiced revisions are excluded; later invoices only charge newly uninvoiced revisions (e.g. if
R02was already invoiced, atR04onlyR03+R04are billed). - Billing is version-unit based, not task-lump based:
R00is one unitR01is one unitR02+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
R00was already invoiced - invoice add-item UI should show a visible type chip for each unit:
NewforR00RevisionforR01+
- 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
R02is now active, uninvoicedR01may still appear whileR02must stay off the invoice list until it is completed/approved
- if a newer version is now active (
- 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
/financesinvoice 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
teamorclient. - The initial request is version
R00. R00request data lives on the initialsubmissionrow.- 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
- derive from
- 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
Submissionstab and may happen multiple times for the sameR##. - Client decision on a review:
Approve:- task moves to
client_approved - task remains on the approved version
- task moves to
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 Notestied to that same version
- task returns to
- A real revision request creates the next visible version:
R00->R01R01->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
Submissionswithout 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 BooksWorking FilesSurvey
- folder creation now works via FileBrowser API using:
- a valid production
FILEBROWSER_TOKEN - FileBrowser source
srv
- a valid production
- folder sync uses FileBrowser only for server folder creation
- this does not restore the old file-sharing page or task folder tab
- when a new company is created by team, the portal ensures a matching server folder exists:
- Initial task request files:
- created by
teamorclient - attach to the initial request in the
Overviewtab - 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
Surveyfolder
- ensure the matching task folder exists under
- initial request files are stored in Supabase under the task's
Surveypath for portal access- storage path format:
{taskId}/Survey/{timestamp}_{originalFileName}
- storage path format:
- 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
Surveyfolder - the live working behavior is:
- request attachments still show inside the portal from Supabase
- the same attachments also appear in the matching server
Surveyfolder
- for
Brand Bookrequests, sign details are stored directly on thesubmissionrow - sign detail payload lives in
submissions.signs - old
submission_signsrows are migrated intosubmissions.signsand no longer used by the portal - sign info in the
Overviewtab renders as plain text rows with no inset background card
- created by
- Revision request files:
- created by
teamorclient - attach to the specific revision entry in the
Revisionstab - stored on the revision
submission - file records live in
submission_files
- created by
- Review submission files:
- created by
teamorsubcontractor - attach to the specific submitted review entry in the
Submissionstab - stored on the
delivery - file records live in
delivery_files
- created by
- 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
R00rejection note shows beside theR00request infoR01rejection note shows beside theR01request infoR02rejection note shows beside theR02request 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
- when a new request is created, it emails:
- In review:
- when a task is placed in review, it emails:
- the client
- all team members
- the assigned user
- when a task is placed in review, it emails:
- On hold:
- when a task is placed on hold, it emails:
- the client
- all team members
- the assigned user
- when a task is placed on hold, it emails:
- Amend request:
- when a request is amended, it emails:
- the client
- all team members
- the assigned user
- when a request is amended, it emails:
- 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
- when a client rejects a task, it emails:
- Approved:
- when a client approves a task, it emails:
- the client
- all team members
- the assigned user
- when a client approves a task, it emails:
- 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
- when a revision request is created, it emails:
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
- team finance hub:
- 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:
R00can bill as new workR01is tracked but freeR02+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
- invoice status is
- 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:
R00is one billable unitR01is one version unit but freeR02+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
R02is now active, uninvoicedR01may still appear whileR02must 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
- if a newer version is now active (
- 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-invoicesstays 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
+ Invoiceaction 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 InvoiceSubmit to Teamwhen still draftDownload Receiptwhen paid
- subcontractor invoice popup line-item table should include:
Work(NeworRevision)R#(R00,R01, and so on)DescriptionType(task service type such asBrand Book,Sign Family, and so on)QtyUnit PriceAmount
- 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:
28px500var(--text-primary)line-height: 1.2
- header subtitle styling follows the global site header spec:
13pxvar(--text-muted)
- two top summary cards are the first content row, using the same
24pxgap 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:
11px500- uppercase
letter-spacing: 0.8pxvar(--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