Audience: Staff (office / PM / field). The Jobs command center at /portal/jobs is the
portfolio-level home for everything project-related β the jobs list, cross-job tasks, the
company schedule, documents, permits, reusable templates, and a live field view. Clients never
see this shell; they get a read-only view of a single job via a shared client_token link.
Status: π’ Live. All seven pane links resolve to real, wired handlers rendering real
templates (verified in app/portal/router.py, app/routers/tasks.py, documents.py,
permits.py, planning.py, field.py). Job creation works through several live paths.
One caveat vs. the plan: the "deep fold" was shipped as a horizontal tab-bar of plain links
to separate full pages (templates/jobs.html:19-33), not the inline Alpine "native panes" /
rail shell the nav plan describes (docs/nav_consolidation_plan.md:26-31). It works; it just
isn't a single-shell SPA.
The shell is the Portfolio page (GET /portal/jobs β jobs_list, app/portal/router.py:341,
template templates/jobs.html). At the top of that page a tab bar (jobs.html:20-33) links out
to the other six tools:
| Pane | Link | Handler | Template |
|---|---|---|---|
| Portfolio | /portal/jobs |
jobs_list (app/portal/router.py:341) |
jobs.html |
| Tasks | /portal/tasks |
tasks_hub (app/routers/tasks.py:73) |
tasks_hub.html |
| Schedule | /portal/schedule |
master_schedule (app/portal/router.py:2431) |
master_schedule.html |
| Documents | /portal/documents |
org_hub (app/routers/documents.py:303) |
documents/β¦ |
| Permits | /portal/permits |
permits_dashboard (app/routers/permits.py:541) |
permits_dashboard.html |
| Templates | /portal/templates |
templates_page (app/routers/planning.py:48) |
templates.html |
| Live view | /field/live |
live_view (app/routers/field.py:369) |
field/live.html |
β οΈ Naming collision to know about. The Jobs "Live view" pane points at
/field/live
(field-crew site map,app/routers/field.py). There is a different feature at/live
(app/routers/live.py) β the marketing "who's on your website right now" PWA. They are
unrelated; don't conflate them.
What it does. The portfolio command center: a searchable/filterable job list (left), a live
preview of the selected job (middle), and a cross-job "needs you" column (right)
(templates/jobs.html:50-260; data assembled in jobs_list, app/portal/router.py:341-401).
A slim KPI band shows active jobs, total, contract value, projected profit, and overdue tasks β
computed only over live projects (drafts and archived are excluded from headline numbers,
app/portal/router.py:375-386). Filters: All / Active / On hold / Complete, plus Drafts and
Archived tabs when those exist.
How-to (open a job). Search or filter the list β click a job β the preview shows progress,
margin/fade (from reporting.job_costing, router.py:363), crews, and upcoming tasks, with quick
links to the job's Command Center, Schedule, Billing, Cost Center, and Procurement folders
(jobs.html:109-142). Drafts show a Save to Jobs button (POST /portal/jobs/{id}/promote)
and blank projects can be restored/deleted inline (jobs.html:147-152).
Audience: staff. Requires an authenticated portal user (current_portal_user); no extra role
gate on viewing.
What it does. A cross-job task hub β every task assigned to you (or "General", not tied to a
job) in one place (tasks_hub, app/routers/tasks.py:73-90; data from task_svc.for_user).
Supports saved views (user.nav_prefs['task_views']), team/sub assignment, and AI task drafting.
How-to (create a task). In the hub, create directly via POST /portal/tasks/new/create
(tasks.py:93) β title required, optional job (else "General"), priority, due date, recurrence,
and multi-assignee (users + subs). Or draft with AI: POST /portal/tasks/ai/generate from a
free-text note, or POST /portal/tasks/ai/from-job from a job's existing activity β both land on
a review screen (tasks_review.html) where nothing is saved until you confirm via
POST /portal/tasks/ai/confirm, which creates only the rows you kept (tasks.py:199-231).
Audience: staff β the whole tasks router is gated require_portal_role(pm, office)
(tasks.py:25-28).
What it does. The company-wide master schedule: every active/on-hold job on one timeline, a
swimlane per job, color-coded by trade (master_schedule, app/portal/router.py:2431-2456;
layout built by app/services/schedule_viz.py::portfolio). Note this portfolio view is
read-oriented; task/date editing happens inside a job's own Gantt (Frappe Gantt built in
job_detail, router.py:1754-1765) and via the schedule JSON API (app/routers/schedule.py,
prefix /api/schedule β create_item, update_item).
How-to. Open the Schedule tab β scan all jobs on the shared timeline β click into a job to
re-plan. Schedule engine/roll-up/heads-up logic lives in app/services/schedule_engine.py,
schedule_rollup.py, schedule_headsup.py.
Audience: staff (authenticated portal user).
What it does. The org-wide Document Hub β all documents across all jobs, with views/filters
by folder, type, phase, trade, tag, and share status (org_hub, app/routers/documents.py:303;
shared renderer _render_hub). The same renderer backs the per-job document hub at
/portal/jobs/{job_id}/documents (documents.py:317).
How-to. Open Documents β filter/search β open a document at /portal/documents/{id}/view.
Uploads, foldering, and sharing are handled by the other routes in documents.py
(e.g. redirects back to /portal/documents after create at documents.py:326).
Audience: staff.
What it does. A company-wide permit tracker β every permit across every project in one table,
with KPIs for open / expiring (β€30d) / expired / missing-docs, and per-permit document readiness
(permits_dashboard, app/routers/permits.py:541-590). The per-job permit workspace (tracker +
AI predictor + BuildData intelligence) lives at /portal/jobs/{job_id}/permits
(permits.py:67, job_permits_page).
How-to. Open Permits β filter by type/status β click through to the job's permit page to add
(POST /portal/jobs/{id}/permits, permits.py:100) or run the AI predictor
(POST β¦/permits/predict, permits.py:352).
Audience: staff β dashboard gated require_portal_role(pm, office) (permits.py:542-543).
Note the dashboard sets active_nav="compliance" (permits.py:589), a leftover from when Permits
lived under a Compliance section β cosmetic, not broken.
What it does. Reusable schedule blueprints (task lists + line items + payment plans) that seed
new jobs (templates_page, app/routers/planning.py:48-62). Supports a visual editor
(template_editor.html), duplicate, delete, save-a-job-as-template, and seeding built-in starter
templates.
How-to. Open Templates β New (/portal/templates/new) or edit
(/portal/templates/{id}/edit) in the visual editor β or Seed starters
(POST /portal/templates/seed-starter, idempotent by name, planning.py:112). From a template
card, Start a job β links to /portal/jobs/new?template={id} (templates.html:123), which
prefills the new-project wizard. Save an existing job as a template via
POST /portal/jobs/{id}/save-as-template (planning.py:65).
Audience: staff.
What it does. The field command view: a real-time snapshot of every located jobsite on a
Leaflet map with who's on site, overdue counts, and a 14-day shift history
(live_view, app/routers/field.py:369-390; data from field_ops.command_snapshot /
shift_history). Rendered full-bleed (field/live.html) as part of the Field App shell
(active_tab="live").
How-to. Open Live view β see active crews/sites; click a mapped site to jump to the job.
Audience: staff, specifically the field/office operator context (_ctx / _base_ctx,
field.py). This is the Field App's shell, surfaced as a Jobs pane.
Opened by clicking a Portfolio row. Route: GET /portal/jobs/{job_id}?view=command
(job_detail, app/portal/router.py:1731) β job_command_center.html. It assembles per-folder
data defensively (each block wrapped in try/except so one failing service never blanks the page,
router.py:2295-2332): Overview, Schedule (Frappe Gantt), Tasks, Billing,
Cost Center (cost_center.assemble), Procurement/PO (change orders via
app/services/change_order.py, co_schedule.py), Permits (permit_cc.assemble),
Labour (labour.assemble), plus People, RFIs, Warranty, Safety, Signatures folders
(link patterns in templates/jobs.html:139-248 and app/routers/field.py).
?view=classic (router.py:2333) falls back to the original 16-tab job_detail.html βapp/routers/daily_logs.py, prefix /api/daily-logs)On /portal/jobs, click β¨ New project (jobs.html:16) β opens the new-project modal
(jobs.html:266-316) offering three routes:
GET /portal/jobs/newapp/portal/router.py:407, jobs_new.html). Enter name + scope βPOST /portal/jobs/new/plan drafts a task plan with AI (router.py:455, degrades gracefullyAIError) β review/edit the plan β POST /portal/jobs/new/create materializes the job viaai_assistant._materialize_project (router.py:506-606), resolving/creating the client inline.POST /portal/jobs/from-photo (app/routers/jobextras.py:908);POST /portal/jobs/from-plan (jobextras.py:968); opens in the plan/portal/proposals/from-plan to make ajobs.html:311).Draft model. The express (photo/plan) lanes create the job as a draft when no client is
chosen yet (is_draft=(cid is None),jobextras.py:932,993); picking/creating a client
promotes it to a real job. Abandoned express drafts are auto-cleaned after 7 days
(_DRAFT_TTL_HOURS=168,app/services/scheduler.py:92).
/portal/jobs/new?lead={id}templates/lead_detail.html:62). The wizard prefills from the lead; on create it stitches back:lead.won_job_id, marks the lead won, logs a LeadActivity, and copies the lead'sapp/portal/router.py:574-593)./portal/jobs/new?template={id}templates.html:123) β POST /portal/jobs/new/from-template renders the editable planapp/routers/planning.py:225).app/services/conversion.py:212 (sets proposal.job_id, marks the lead won, attaches_h_create_jobapp/services/ai_assistant-driven, app/services/ai_bar.py:270).POST /api/jobs (app/routers/jobs.py:30), gatedrequire_roles(pm, office). The backing OpenProject Project is created lazily (only when thePOST /api/jobs/{id}/provision (jobs.py:65).nav_consolidation_plan.md:16-31).<a> linksjobs.html:20-33). Functional, but not the single-shell fold that was scoped.view=classic job page is legacy. job_detail.html (the original 16-tab page) is stillapp/portal/router.py:2332-2334).app/routers/jobs.py:1-4,65-78); if OpenProject is unreachable, jobs still create fine. Worthactive_nav="compliance" on the Permits dashboard (permits.py:589) is a stale label from/live (marketing visitor PWA, app/routers/live.py) vs /field/liveapp/routers/field.py:369) β easy to confuse; only the latter is a Jobs pane./api/jobs, /api/schedule, /api/daily-logs are JSON APIs parallel to the portal HTML