0104e877501b73192be1ee2b45f864994c9d308b
9 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d4ef4d55e0 |
fix(fusion_repairs): wrap Wysiwyg content with markup() so HTML renders, not escapes
User reported the rich text editor showing raw HTML tags as literal text
instead of rendering them as formatted prose. Root cause: Odoo's Editor
delegates content insertion to setElementContent() (web/core/utils/html.js),
which only takes the innerHTML branch when the content was flagged as safe
markup via owl's markup() helper. Plain strings fall through to the
textContent branch, which is what the user was seeing:
<p>Ask the client if the stairlift has power. Check:</p> <ul> <li>...
instead of the rendered paragraph + list.
The canonical html_field.js in @html_editor wraps its value with markup()
before passing it to the Wysiwyg config; I missed that detail.
FIX
- import markup from @odoo/owl
- in wysiwygConfig getter, wrap the saved content_html string with
markup() before assigning to config.content
- pass markup("") for empty content (avoids editor confusion with falsy)
- load-bearing comment to keep future refactors from re-introducing the bug
VERIFIED
- upgrade clean
- 7 stale asset bundles flushed, container restarted, login serves 200
- new bundle 014fee9 renders 10029808 bytes
- node --check PARSE_OK
- compiled bundle contains: content:rawHtml?markup(rawHtml):markup("")
which is exactly the markup-wrapped path the Editor wants
Bumped to 19.0.2.2.4.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
||
|
|
2414b6328e |
fix(fusion_repairs): designer setup() scope - onMounted/onWillUnmount were stranded outside, broke entire backend bundle
REGRESSION FROM
|
||
|
|
b22bb11b31 |
feat(fusion_repairs): flowchart designer node content uses Odoo Wysiwyg
Replace the plain <textarea> in the flowchart designer's node-editor
right-panel with Odoo 19's native rich text editor so admins write
formatted prose / lists / bold / links / inline images without typing
HTML tags. The raw <textarea> stays available behind a toggle for the
power-user case (pasting markup from elsewhere, debugging).
CHANGES
manifest:
- depends += 'html_editor' (provides @html_editor/wysiwyg)
- bumped to 19.0.2.2.1
components/flowchart_designer/flowchart_designer.js:
- import { Wysiwyg } from '@html_editor/wysiwyg'
- import { MAIN_PLUGINS } from '@html_editor/plugin_sets'
- register Wysiwyg in static components
- state.sourceMode boolean (default false = rich text mode)
- wysiwygConfig getter builds the EditorConfig for the SELECTED node;
onChange reads editor.getContent() and writes back into the same
selectedMeta.content_html the rest of the designer already uses,
so the save path is unchanged
- onWysiwygLoad(editor) captures the editor instance per dfId so the
onChange callback can resolve the right one when nodes switch
- onToggleSource flushes the current editor's content before flipping
modes so unsaved keystrokes don't get lost
components/flowchart_designer/flowchart_designer.xml:
- replaced <textarea>...</textarea> with a conditional block:
sourceMode == false -> <Wysiwyg t-key="'wysiwyg-' + selectedNodeId"
config="wysiwygConfig"
onLoad="onWysiwygLoad.bind(this)"/>
sourceMode == true -> <textarea class="font-monospace" rows="10"/>
- t-key forces the editor to re-mount with the freshly-selected node's
content; otherwise switching nodes would keep showing the first
selected node's HTML
- new toolbar row above the editor has a "HTML Source" / "Rich Text"
toggle button (eye / code icons) so the user can flip at will
- hint text updated to reflect what each mode supports
components/flowchart_designer/flowchart_designer.scss:
- widened the right editor panel from 320px to 360px to give the
Wysiwyg toolbar room to breathe
- new .fr-wysiwyg-shell rule frames the embedded editor with the same
border + background as the other form-controls in the panel, with
a min-height of 180px and max-height 320px so it scrolls when the
content grows. Pins .o-we-toolbar inside the shell so it stays in
view as the user scrolls long content.
The save path, the runtime renderer, and the data model are unchanged -
content_html is still sanitised HTML stored on fusion.repair.flowchart.node.
Verified on local westin-v19:
- upgrade clean (no errors, no warnings)
- login serves 200 after restart
- 4 stale asset bundles flushed; Drawflow JS still served 46KB at
/fusion_repairs/static/src/lib/drawflow/drawflow.min.js
- Wysiwyg export confirmed at
/usr/lib/python3/dist-packages/odoo/addons/html_editor/static/src/wysiwyg.js:25
- MAIN_PLUGINS export confirmed at plugin_sets.js:103
Bumped to 19.0.2.2.1.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
||
|
|
eb186cac3c |
feat(fusion_repairs): Bundle 11 - CS guided troubleshooting flowcharts + vendor PO
Two big workflow additions:
1. Visual drag-and-drop flowchart designer (Drawflow) + card-by-card runner
(with show-whole-tree toggle) so admins build per-(category, symptom)
decision trees with embedded photos/videos and CS walks callers through
them on the phone. Resolved-on-call closes the repair; escalated copies
the full transcript into internal_notes so the dispatched tech sees what
was already tried before they arrive at the client.
2. Vendor + draft-PO + factory-tracking on the part-order capture. Tech on
the phone with the factory picks the vendor from contacts, types the OEM
part #, cost, ETA date (calendar widget), factory ticket #, RA #, ticks
under_warranty, and the system auto-creates a draft purchase.order with
the right product (looked up or created from OEM) + activity for the
office on the ETA day + client email with ETA prominently shown and
cost intentionally omitted.
NEW MODELS
fusion.repair.symptom.class - lookup table (category + name + code).
Replaces the flat x_fc_issue_category Char on repair.order. Seeded with
7 stairlift symptoms + lighter coverage for hospital bed / porch lift /
lift chair. Equipment Class added to fusion.repair.product.category
(this carried over from the Bundle 10 plan).
fusion.repair.flowchart + .node + .edge - design-time graph.
- flowchart has name, category, symptom, version, published flag,
canvas_layout (Drawflow JSON), node_ids, edge_ids, computed start_node
- node has node_type (question / suggestion / info / outcome),
content_html, media_ids (M2M ir.attachment for photos + videos),
is_start, outcome_kind (resolved / escalate / order_part),
canvas_x/y for Drawflow round-trip
- edge has source, target, label, sequence - supports N-ary branching
(not just Yes/No)
- designer_load() and designer_save(payload) RPC API the OWL component
consumes; save is atomic-replace + bumps version + soft-validates
fusion.repair.flowchart.run + .step - runtime sessions.
- One run per repair, audited; runtime_start_or_resume() returns the
existing in-progress run or creates a fresh one for the matching chart
- runtime_choose(edge_id, cs_note) records a step + advances current_node
- runtime_complete(outcome) snapshots final node + calls _apply_outcome:
resolved -> auto-close via action_repair_start + action_repair_end,
set x_fc_resolved_on_call, post transcript to chatter
escalated -> prepend transcript to repair.internal_notes so the tech
sees it first when they open the form
order_part -> chatter note; tech opens visit-report wizard next
abandoned -> just save transcript
- Each step snapshots node_name + chosen_label at write time so the
transcript survives later chart edits without breaking.
REPAIR.ORDER EXTENSIONS
- x_fc_symptom_class_id (M2O) - new structured symptom field
- x_fc_resolved_on_call (Boolean, tracked) - true after a resolved outcome
- x_fc_flowchart_run_ids + x_fc_flowchart_run_count
- action_start_troubleshoot() - opens the runner client action, raises a
helpful UserError if no symptom set or no published chart exists
- action_view_flowchart_runs() smart button
- x_fc_issue_category renamed string to "(legacy)" - kept for back-compat
+ AI prompt context; new intakes set the M2O
DRAWFLOW DESIGNER (OWL)
static/src/lib/drawflow/drawflow.min.{js,css} - vendored Drawflow 0.0.59
(MIT). Loaded only in web.assets_backend, ~48KB total.
components/flowchart_designer/flowchart_designer.{js,xml,scss}:
- Client action "fusion_repair_flowchart_designer" with full drag-drop
canvas + zoom + pan
- 4 custom node templates color-banded by type (question blue,
suggestion green, info gray, outcome red/green/amber per outcome_kind)
- Right-panel editor for selected node: title, type, outcome kind,
content (HTML), media uploader (drag-drop or click), set-as-start
toggle, per-outgoing-edge label editor
- Save serializes Drawflow JSON to canvas_layout + atomic-replaces the
structured node/edge rows via the designer_save RPC
CARD RUNNER (OWL)
components/flowchart_runner/flowchart_runner.{js,xml,scss}:
- Client action "fusion_repair_flowchart_runner"
- DEFAULT MODE: card-by-card. One big card per node, embedded photos +
inline <video controls>, answer buttons sized for phone use, CS note
textarea (saved as cs_note on the step), running transcript at the
bottom
- TOGGLE: "Show Whole Tree" loads the same Drawflow lib in read-only
fixed mode, imports the canvas_layout JSON, highlights current node
yellow / visited green via .fr-current / .fr-visited classes
- Outcome buttons drive the right runtime_complete() call; success
notifications + auto-return to the parent repair form
- "Abandon & Escalate" header button at all times - transcript is saved
even on bail-out so the dispatched tech still benefits
PART ORDER + VENDOR PO
repair.part.order new fields:
vendor_partner_id (M2O res.partner, is_company domain), purchase_order_id
(auto-created draft PO), product_id (auto-resolved or created),
unit_cost (Monetary) + currency_id, internal_po_ref, factory_ticket_ref,
factory_ra_number, under_warranty.
action_create_draft_po() - resolves product.product by OEM (default_code)
or creates a new one in a "Spare Parts" product.category, creates a
purchase.order in draft state with one line (product + qty + price_unit
+ date_planned from expected_date or +7d), stamps Westin's internal PO
ref as partner_ref so the factory can find it on return. Office reviews
and confirms via the normal Odoo flow.
_schedule_eta_activity() - schedules a Repair: Assign Technician activity
on the parent repair.order due on expected_date, assigned to
repair.user_id, so the office is reminded to call the client and book
the return visit on the day parts arrive.
VISIT-REPORT WIZARD PARTLINE EXTENSIONS
Same new fields exposed inline on the partline list so the tech captures
everything on the phone with the factory in one form:
vendor_partner_id (vendors-only filter), unit_cost + currency,
expected_date (calendar widget) replacing expected_lead_days as the
preferred input, under_warranty, internal_po_ref, factory_ticket_ref,
factory_ra_number, create_draft_po (default True - auto-builds PO on
submit when vendor + cost are both set).
CLIENT EMAIL TIGHTENED
email_template_parts_ordered:
- Subject now includes ETA "Parts ordered for your stairlift - expected 2026-06-06"
- Hero ETA panel: large blue-bordered card with "Expected Arrival" label
and the date in 24px bold
- Cost INTENTIONALLY OMITTED - "Our office will call you to confirm a
return visit time. If you have any questions about pricing or
scheduling, please reach out to our office directly."
- "There is nothing for you to do right now." callout
UI
- repair.order form header: new "Start Troubleshooting" button (info
style, sitemap icon, visible when state in (draft, confirmed,
under_repair) AND symptom is set)
- repair.order form intake row: x_fc_symptom_class_id picker filtered to
the category, x_fc_resolved_on_call display when true
- repair.part.order form: header button "Create Draft Purchase Order"
+ new Vendor / Cost / Warranty group + System group with the PO link
- Intake wizard equipment line: symptom_class_id picker
- New menus:
Configuration > Symptom Classes
Configuration > Troubleshooting Flowcharts
Fusion Repairs > Troubleshooting Sessions (run history)
SECURITY
18 new ACL rows for the 6 new models, scoped Manager-full / User-read /
FieldTech-read. Flowchart runs and steps get write access for User so CS
can record steps; Manager owns flowchart + node + edge CRUD.
POST-MIGRATION (19.0.2.2.0)
Existing installs: walks all distinct (category, x_fc_issue_category) text
pairs on repair.order, creates a placeholder fusion.repair.symptom.class
per pair (or reuses an existing match by code/name), back-fills the new
x_fc_symptom_class_id M2O. Idempotent + safe to re-run.
DEPENDENCY
Added 'purchase' to depends (action_create_draft_po needs purchase.order).
VERIFIED END-TO-END on local westin-v19 (Margaret persona, 0 bugs):
STEP 0 seed: chart v1 8 nodes / 12 edges / published, 7 stairlift
symptoms, stairlift class=lift_elevating
STEP 1 CS creates RO-202605-60 with symptom Not Moving
STEP 2 Start Troubleshooting -> client action tag returned
STEP 3 walk run: Power on? Yes -> Seatbelt? Yes -> Swivel? Yes ->
outcome 'Still not moving - dispatch technician'
(outcome_kind=escalate)
STEP 4 runtime_complete('escalated') -> internal_notes prepended with
CS troubleshooting summary
STEP 5 visit-report parts_needed with vendor Handicare + cost $425 +
warranty + factory refs -> PART-00008 created + draft
PO 26690 auto-built with line "Handicare 1100 control
board" qty 1 @ $425, partner_ref WH-2026-1042
STEP 6 mark_ordered -> client email queued (NO cost mentioned, ETA
shown prominently) + office activity scheduled for
2026-06-06
STEP 7 fresh resume returns same run; resolved outcome auto-closes the
repair (state=done, x_fc_resolved_on_call=True)
Bumped to 19.0.2.2.0.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
||
|
|
4f1b7c2df6 |
fix(fusion_repairs): persona-driven workflow audit - 6 real bugs
Full end-to-end walk acting as customer, CS rep, dispatcher, technician,
and manager surfaced 6 real bugs (1 critical state-machine, 4 missing UX
wires, 1 docstring). Server endpoints existed for everything but several
were not wired into the templates.
B1 (HIGH) - Visit-report wizard never closed the repair
Tech submitted visit -> state stayed 'draft' -> x_fc_done_at never
stamped -> NPS cron never fired -> the whole post-visit flow died
silently. Customers never got their NPS email.
Fix: action_confirm() now drives the Odoo native state machine
draft -> action_validate (with _action_repair_confirm fallback) ->
action_repair_start -> action_repair_end. Each step guarded by the
current state and exception-logged. Leaves the repair open if:
- requires_requote=True (variance flag - office must re-quote)
- no_show=True (office reschedules)
- x_fc_is_quote_only (still a quote)
- found_another_issue spawned a stub
Posts a clear chatter line on success or failure.
Verified: e2e walk now shows state=done + x_fc_done_at stamped +
NPS cron fires + flags x_fc_nps_email_sent=True.
B2 (HIGH) - /repair/new form never called /repair/self_check
The AI self-check engine was the headline weekend feature but it was
invisible to the client. The endpoint worked server-side, just had
no frontend.
Fix: new portal_client_repair.js (Interaction class, registered on
registry.category('public.interactions')). 'Try 1-3 safe self-check
steps first' button POSTs to /repair/self_check, renders steps via
createElement + textContent (no innerHTML - all server output is
treated as untrusted text). Shows the AI's safety disclaimer on
every result. On escalate_immediately, shows a clear 'submit the
form, we'll come to you' message instead of the steps.
Verified: HTTP POST returns full JSON with instruction +
expected_result + disclaimer; new button + result panel appear in
rendered HTML.
B3 (HIGH) - No phone-lookup UI for returning clients
Same problem - endpoint existed but no UI. Returning clients had to
retype everything from scratch.
Fix:
- lookup_phone now returns a 'partners' array (id, name, email,
street, city) - cap of 3 results, rate-limited, every match logged
at INFO level for audit. Privacy compromise: a phone holder
deserves to see their own pre-fill; rate limit caps harvesting.
- JS lookup widget at the top of the form posts to /repair/lookup_phone
and pre-fills the 5 contact fields + writes the partner_id to a
hidden #fr_known_partner_id input.
- controller /repair/submit now trusts known_partner_id if present
(skips the phone re-match) so we don't create duplicate partners
when the lookup widget already identified the right one.
Verified: HTTP POST returns the 2 partner records we have for
+19055551234 with full id/name/email/street/city.
B4 (MEDIUM) - /repair?sn=<serial> from QR sticker did nothing
Spec: 'Client scans QR sticker - portal pre-fills the unit info.'
Reality: the form had no serial field; ?sn= was ignored.
Fix: new _resolve_serial_info(serial) on the controller resolves
the lot via stock.lot.search([('name','=',sn)]) and returns
{serial, lot_id, product_id, product_name, category_id}. Both
/repair (landing) and /repair/new pass it as serial_info template
context. Templates show 'Recognized X (Serial: Y)' + auto-select
the matching category in the dropdown. Hidden #fr_serial_number
carries it through to /repair/submit, which attaches the lot_id +
uses the QR category as fallback if user didn't pick one.
Verified: ?sn=stella23-20040164 produces 'Pre-filled from QR scan:'
banner + hidden input populated.
B5 (MEDIUM) - No upsell after submit
Spec required an upsell - 'reduce future calls'. Page was a bare
'Got it'.
Fix: /repair/thanks now shows a 2-card layout:
- 'Want to avoid this next time?' with 4 bullets (priority booking,
free inspection cert, discounted parts, annual reminder) +
'See our maintenance plans' CTA to /shop?category=maintenance
- 'What happens next' 4-step bulleted explanation
Verified: both cards render.
B6 (LOW) - SyntaxWarning '\-->' in repair_service_plan.py
Made the module docstring a raw string (r''') so the ASCII flowchart
arrows don't trigger Python's invalid-escape-sequence warning.
Bumped to 19.0.1.8.0.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
||
|
|
638b223d3b |
feat(fusion_repairs): Bundle 6 - M7 failure analytics + M9 margin per repair
M9 margin per repair
- New non-stored computes on repair.order: x_fc_revenue, x_fc_labour_cost,
x_fc_parts_cost, x_fc_margin, x_fc_margin_pct.
- Revenue: sum of posted out_invoice.amount_untaxed on the repair's sale
order (handles partial / multi invoice scenarios).
- Labour: sum of (task.duration_hours x technician.x_fc_tech_cost_rate)
over COMPLETED visits only - avoids counting scheduled-but-not-done time.
- Parts: sum of standard_price x qty for stock moves where
repair_line_type='add' (parts consumed, not removed).
- New 'Margin' notebook tab on repair.order form, manager-group gated.
M7 failure analytics on the dashboard
- Three new keys in get_dashboard_data():
* failures_by_product - top 8 products by repair_count in last 90 days
via _read_group (efficient - no record load)
* failures_by_symptom - top 8 x_fc_issue_category values
* margin_summary - revenue/labour/parts/margin/margin_pct + sample_size
over the same 90-day window
- Three new tiles on the OWL dashboard 'Last 90 Days' section:
Margin Summary (revenue/labour/parts/margin breakdown),
Failure Rate by Product, Failure Rate by Symptom.
- New formatMoney + formatPercent helpers on the dashboard JS so values
display as 'CAD 12,345' rather than raw floats.
Verified end-to-end on local westin-v19:
Dashboard returned all 9 expected keys.
Top product: 'M6 X 27 THREADED BARREL' (2 repairs) - actual test data.
Margin summary over 26 repairs (dev has $0 invoices so values 0.0,
but the compute path is exercised and shapes are correct).
Bumped to 19.0.1.6.0.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
||
|
|
7f8a80fecb |
fix(fusion_repairs): dashboard scrolling
The dashboard root used min-height: calc(100vh - 46px) which expanded to the viewport but bypassed the parent .o_action_manager flex sizing, so the inner overflow-y: auto had nothing to scroll - vertical content was clipped or stuck. Replaced with height: 100% + overflow-y: auto + overflow-x: hidden so the component fills its action container and scrolls naturally. Bumped to 19.0.1.0.6 to bust the asset bundle hash. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
38a79a4b04 |
feat(fusion_repairs): OWL dashboard - quick actions, KPIs, portal share
A real landing dashboard for the Fusion Repairs app so users see at a glance what is open, what is urgent, and where to click. Built as an OWL client action, theme-aware (light AND dark) at SCSS compile time, zero hardcoded user-facing colours. What's on it - Hero banner with gradient accent - 4 quick-action tiles (New Service Call, Service Calls, Maintenance Contracts, Repair Warranties) - 6 KPI stat tiles (Open / Urgent+Safety / Awaiting Dispatch / Needs Re-Quote / New This Month / Maintenance Due 30d) - each is clickable and lands in the right filtered list - Self-service portal cards with copy-to-clipboard for the public client portal URL and the sales rep portal URL (so office can share them on voicemail / printed materials / training) - Recent Service Calls list (last 5) - click jumps to repair form - Upcoming Maintenance list (next 5 due) - red pill when <=7 days out - Configuration tiles (Equipment Categories / Intake Templates / Service Catalogue) - Refresh button Architecture - fusion.repair.dashboard AbstractModel exposes get_dashboard_data(): returns stats + urgency_breakdown + source_breakdown + recent[5] + upcoming[5] + portals (URLs resolved via web.base.url + fusion_repairs.client_portal_url) - FusionRepairsDashboard OWL component (registry actions 'fusion_repairs.dashboard') uses standalone rpc() per project rule #3, useService('action') for navigation, useService('notification') for copy feedback. static props = ['*'] to accept the client-action props envelope. - _fr_tokens.scss registered FIRST in web.assets_backend so its variables are in scope when dashboard.scss compiles. NO @import (per project rule). Branches on $o-webclient-color-scheme at compile time so the dark bundle (web.assets_web_dark) gets dark hex values automatically - per project CLAUDE.md rule on dark mode. - All visible colours come from CSS-variable-wrapped SCSS tokens (--fr-page-bg, --fr-card-bg, --fr-border, --fr-accent, ...) which fall back to the SCSS hex value. Three-layer contrast: page (grayest) -> card (mid) -> elevated (brightest). - New ir.actions.client action_fusion_repairs_home_dashboard with tag='fusion_repairs.dashboard'. - Top-level menu now lands on this dashboard. 'Dashboard' added as the first sub-menu; 'Service Calls' (the kanban) is still right below it. Verified on local westin-v19: STATS: open=15, urgent=4, new_this_month=13, awaiting_dispatch=9, requires_requote=1, maintenance_due_30d=1, active_total=2 PORTALS: client=http://192.168.139.165:8069/repair sales_rep=http://192.168.139.165:8069/my/repair/new RECENT count: 5 UPCOMING count: 2 SOURCE breakdown: backend_wizard 9, client_portal 3, manual 2, sales_rep_portal 1 Web /web/login: 200, no SCSS compile errors in logs. Bumped to 19.0.1.0.5 so the asset bundle hash refreshes. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
ad553b1082 |
feat(fusion_repairs): Phase 1 sales rep + public client portals
Both portals share the existing fusion.repair.intake.service so behaviour
stays identical across all three intake surfaces (backend wizard,
sales rep portal, public client portal).
Sales rep portal
- Hard depends on fusion_authorizer_portal (reuses is_sales_rep_portal
flag + group_sales_rep_portal scaffolding)
- /my/repair/new - mobile-friendly intake form with phone-first
partner search (jsonrpc lookup), category select, third-party flag,
urgency, photo capture
- /my/repairs - list of repairs the rep submitted (paginated)
- /my/repair/<id> - read-only detail with status, equipment, scheduled
visit
- Interaction-class JS (Odoo 19 public.interactions), safe DOM construction
- Mobile SCSS with 44px tap targets, sticky CTA on small screens
- Record rule scopes portal users to repairs where
x_fc_intake_user_id = user.id
Public client portal
- auth='public' - voicemail-ready /repair URL
- /repair - landing page with 911 disclaimer and Start CTA
- /repair/new - single-page form: contact, equipment, issue, urgency,
optional photos. QR pre-fill via ?sn=<serial>
- /repair/submit - CSRF + honeypot + per-IP rate limit (configurable);
finds or creates partner; calls intake service with sudo
- /repair/thanks - confirmation with reference number
- /repair/lookup_phone (jsonrpc) - safe partner match returning ONLY
masked name (first + last initial) + city (no other PII leakage)
Security fix: technician record rule on repair.order now uses STORED
fields (technician_id + additional_technician_ids) instead of the
non-stored all_technician_ids compute, which was failing SQL generation.
Verified end-to-end on local westin-v19:
- Sales rep create via intake service with the rep user context creates
the repair with x_fc_intake_source='sales_rep_portal' and proper
activities
- /repair/submit posts urlencoded data -> creates partner + repair
('BR-WA/RO/00010', source='client_portal', urgency='urgent') ->
redirects to /repair/thanks with the reference
Co-authored-by: Cursor <cursoragent@cursor.com>
|