Planned
Radon chart out of sync after inspector reassignment
Objective Keep the radon equipment chart (/tools/radon) accurate when radon-related calendar events are moved to a different inspector or corrected after the fact. Align chart totals per inspector with the underlying radon events (and with the calendar radon overlay where both are used). Reduce manual rework (e.g. resetting many events) when inspector assignment or historical baselines drift. Background Operations reported that the radon chart does not reflect events moved from one inspector row to another on the calendar; after correcting ~15 events manually, aggregate counts still did not match the event list—suggesting a disconnect between chart math and stored events. The chart is not directly editable: cell values are computed from (1) yesterday’s ending counts per inspector (RadonEnds), plus (2) radon events in the date window (event.radon.number, including equipment-derived radon), bucketed by current event._inspectorId. Calendar save (ChangedEventIndicator → PATCH event/:id) should persist a new _inspectorId, but the radon chart page does not auto-refresh after calendar saves (only after Equipment Transfer on the radon page). Radon data (/tools/radon/data) lists the same events grouped by job/inspection, not by inspector row—so it is normal for it to look different from the chart even when data is correct. Job-level inspector changes with “reassign events” only move incomplete future events (endtime > now in attik-backend/src/routes/inspection.ts); past radon pickups stay on the original inspector until moved on the calendar. Until a fix ships, teams can correct totals via Settings → Radon ending numbers, Equipment Transfer, and calendar drag + save + chart refresh (see internal ops note in team channel). Original report: Slack message Scope Backend Chart data is built in attik-backend/src/routes/radon.ts (GET /radon/radon-chart-data): loads legacy radon events and equipment-derived radon via findRadonEquipmentEventsForDateRange / mergeLegacyAndEquipmentRadonEvents in attik-backend/src/util/functions/radon/radonEquipmentBridge.ts, seeds each inspector from RadonEnds, then buckets by strict data._inspectorId === insp._id.toString() and dayjs time slots. Ending-number CRUD lives on the same router (GET/PATCH /radon/ends/:id); nightly rollup uses updateRadonEndsFromYesterday in radon.ts. Event updates use attik-backend/src/routes/event.ts (PATCH /event/:id); _inspectorId on attik-backend/src/models/eventSchema.ts is a string. Calendar API radon overlay uses attik-backend/src/routes/calendar.ts with enrichCalendarEventsWithEquipmentRadon and synthetic radonEnds for future dates. Decision needed: Normalize inspector matching and time-slot generation (chart uses strict equality and server dayjs() for slots while dateCheck uses company TZDayjs for the query window). Frontend Radon chart UI: attik-frontend/src/app/tools/radon/RadonChartBase.tsx (fetches radon/radon-chart-data), RadonContext.tsx (refreshChart / setRefreshChart), read-only cells in RadonCell.tsx. Equipment Transfer (creates transfer events, refreshes chart): attik-frontend/src/app/tools/radon/CalibrationModel.tsx, opened from CornerMenuWrapper.tsx (?calibration=new). Ending number edits (baseline per inspector/date): attik-frontend/src/app/tools/settings/radon/EndEditting.tsx → PATCH radon/ends/:id, embedded in RadonSettingsForm.tsx at /settings/radon. Event audit / transfer cleanup: attik-frontend/src/app/tools/radon/data/page.tsx, EventGroup.tsx (delete some transfer rows). Calendar reassignment: attik-frontend/src/components/calendar/handleChangeEventFn.tsx, EnhancedDragHandler.tsx, replaceEventsWithChangedEvents.ts, save via ChangedEventIndicator.tsx; radon row counts in CalendarRowGrid.tsx use cal._inspectorId.includes(employeeId) (looser than chart ===). Decision needed: Refresh chart after calendar event save; ensure calendar PATCH always sends _inspectorId as a string (drag handlers can produce a single-element array). Out of scope (unless expanded) Client portal, mobile. Replacing the cumulative chart model with direct cell editing (product change). Acceptance criteria After moving a radon event to another inspector on the calendar and saving, the event appears on the correct inspector row on /tools/radon without requiring a full browser cache clear (refresh or automatic re-fetch is acceptable). Chart totals for a test inspector match the sum of that inspector’s radon events (plus configured transfers/ends) for the same window used by /tools/radon/data. Inspector matching and time buckets behave consistently in company timezone. Documented ops path remains valid until release (ending numbers + equipment transfer + calendar reassignment). References Slack — original report attik-backend/src/routes/radon.ts attik-backend/src/util/functions/radon/radonEquipmentBridge.ts attik-backend/src/routes/event.ts attik-frontend/src/app/tools/radon/RadonChartBase.tsx attik-frontend/src/components/calendar/CalendarRowGrid.tsx attik-frontend/src/components/calendar/ChangedEventIndicator.tsx attik-frontend/src/app/tools/settings/radon/EndEditting.tsx
Linear 1 day ago
Main App
Planned
Radon chart out of sync after inspector reassignment
Objective Keep the radon equipment chart (/tools/radon) accurate when radon-related calendar events are moved to a different inspector or corrected after the fact. Align chart totals per inspector with the underlying radon events (and with the calendar radon overlay where both are used). Reduce manual rework (e.g. resetting many events) when inspector assignment or historical baselines drift. Background Operations reported that the radon chart does not reflect events moved from one inspector row to another on the calendar; after correcting ~15 events manually, aggregate counts still did not match the event list—suggesting a disconnect between chart math and stored events. The chart is not directly editable: cell values are computed from (1) yesterday’s ending counts per inspector (RadonEnds), plus (2) radon events in the date window (event.radon.number, including equipment-derived radon), bucketed by current event._inspectorId. Calendar save (ChangedEventIndicator → PATCH event/:id) should persist a new _inspectorId, but the radon chart page does not auto-refresh after calendar saves (only after Equipment Transfer on the radon page). Radon data (/tools/radon/data) lists the same events grouped by job/inspection, not by inspector row—so it is normal for it to look different from the chart even when data is correct. Job-level inspector changes with “reassign events” only move incomplete future events (endtime > now in attik-backend/src/routes/inspection.ts); past radon pickups stay on the original inspector until moved on the calendar. Until a fix ships, teams can correct totals via Settings → Radon ending numbers, Equipment Transfer, and calendar drag + save + chart refresh (see internal ops note in team channel). Original report: Slack message Scope Backend Chart data is built in attik-backend/src/routes/radon.ts (GET /radon/radon-chart-data): loads legacy radon events and equipment-derived radon via findRadonEquipmentEventsForDateRange / mergeLegacyAndEquipmentRadonEvents in attik-backend/src/util/functions/radon/radonEquipmentBridge.ts, seeds each inspector from RadonEnds, then buckets by strict data._inspectorId === insp._id.toString() and dayjs time slots. Ending-number CRUD lives on the same router (GET/PATCH /radon/ends/:id); nightly rollup uses updateRadonEndsFromYesterday in radon.ts. Event updates use attik-backend/src/routes/event.ts (PATCH /event/:id); _inspectorId on attik-backend/src/models/eventSchema.ts is a string. Calendar API radon overlay uses attik-backend/src/routes/calendar.ts with enrichCalendarEventsWithEquipmentRadon and synthetic radonEnds for future dates. Decision needed: Normalize inspector matching and time-slot generation (chart uses strict equality and server dayjs() for slots while dateCheck uses company TZDayjs for the query window). Frontend Radon chart UI: attik-frontend/src/app/tools/radon/RadonChartBase.tsx (fetches radon/radon-chart-data), RadonContext.tsx (refreshChart / setRefreshChart), read-only cells in RadonCell.tsx. Equipment Transfer (creates transfer events, refreshes chart): attik-frontend/src/app/tools/radon/CalibrationModel.tsx, opened from CornerMenuWrapper.tsx (?calibration=new). Ending number edits (baseline per inspector/date): attik-frontend/src/app/tools/settings/radon/EndEditting.tsx → PATCH radon/ends/:id, embedded in RadonSettingsForm.tsx at /settings/radon. Event audit / transfer cleanup: attik-frontend/src/app/tools/radon/data/page.tsx, EventGroup.tsx (delete some transfer rows). Calendar reassignment: attik-frontend/src/components/calendar/handleChangeEventFn.tsx, EnhancedDragHandler.tsx, replaceEventsWithChangedEvents.ts, save via ChangedEventIndicator.tsx; radon row counts in CalendarRowGrid.tsx use cal._inspectorId.includes(employeeId) (looser than chart ===). Decision needed: Refresh chart after calendar event save; ensure calendar PATCH always sends _inspectorId as a string (drag handlers can produce a single-element array). Out of scope (unless expanded) Client portal, mobile. Replacing the cumulative chart model with direct cell editing (product change). Acceptance criteria After moving a radon event to another inspector on the calendar and saving, the event appears on the correct inspector row on /tools/radon without requiring a full browser cache clear (refresh or automatic re-fetch is acceptable). Chart totals for a test inspector match the sum of that inspector’s radon events (plus configured transfers/ends) for the same window used by /tools/radon/data. Inspector matching and time buckets behave consistently in company timezone. Documented ops path remains valid until release (ending numbers + equipment transfer + calendar reassignment). References Slack — original report attik-backend/src/routes/radon.ts attik-backend/src/util/functions/radon/radonEquipmentBridge.ts attik-backend/src/routes/event.ts attik-frontend/src/app/tools/radon/RadonChartBase.tsx attik-frontend/src/components/calendar/CalendarRowGrid.tsx attik-frontend/src/components/calendar/ChangedEventIndicator.tsx attik-frontend/src/app/tools/settings/radon/EndEditting.tsx
Linear 1 day ago
Main App
HubSpot deals not updating to completed stage
Some duplicated HubSpot deals show as published but do not update the deal stage to completed. A manual resync was run and no changes were observed. Examples: Inspection 1008593823 Inspection 1008593533 Inspection 1008593237 Inspection 1008592557 Inspection 1008591887
Linear 1 day ago
Main App
HubSpot deals not updating to completed stage
Some duplicated HubSpot deals show as published but do not update the deal stage to completed. A manual resync was run and no changes were observed. Examples: Inspection 1008593823 Inspection 1008593533 Inspection 1008593237 Inspection 1008592557 Inspection 1008591887
Linear 1 day ago
Main App
Planned
Online scheduler “+N more” doesn’t expand hidden time slots (Top Choice)
Objective Make the online scheduler calendar show all bookable times on busy days, not only the first five. Fix the “+N more” overflow control on the Choose Your Time Slot step so it actually expands (or is replaced with working UX). Unblock Top Choice Inspection Services testing and launch: 1:00 PM (and other times) are available on inspector schedules but hidden behind a non-functional “+2 more” label. Background Neil reported on the Top Choice online scheduler: at the time-picking step, a control that looks like an expander (“+2” / “+1”) does not respond when clicked. 1:00 PM availability is missing from the calendar even though Sean and David both have 1:00 on the internal schedule. Top Choice uses many custom start times, so a single day often has more than five distinct slot times from schedule/optimal-slots. Slots are returned by the API; the calendar truncates display to five per day and shows static “+N more” text with no click handler—users interpret it as broken. Repro walkthrough: Loom — Top Choice Online Scheduler Time Slot Not Showing Transcript summary (Neil): On Top Choice online scheduler, reached time selection. “Plus 2” / “plus 1” expander GUI does not work. 1:00 not shown; Sean and David both have 1:00 on the schedule. Expectation: clicking +2 / +1 should expand to show those times. Scope Frontend (attik-frontend) Public flow: /scheduler → Choose Your Time Slot in src/app/scheduler/slots/SlotsStep.tsx, rendered by src/app/scheduler/slots/CalendarView.tsx. src/app/scheduler/constants.ts sets MAX_VISIBLE_SLOTS_PER_DAY = 5; renderDayCell only maps slotsForDay.slice(0, MAX_VISIBLE_SLOTS_PER_DAY). When slotsForDay.length > MAX_VISIBLE_SLOTS_PER_DAY, UI renders a non-interactive label (+{count} more) around lines 694–697 in CalendarView.tsx—no expand state, no onClick / onPress. Slot times are grouped by exact datetime in slotsByDate; each custom start time counts toward the five-slot cap. SlotsStep passes autoSelectTopInspector={true}—separate from this bug: affects inspector picker on multi-inspector rows, not revealing hidden times. In scope: working expand/collapse per day (or equivalent) so overflow times (e.g. 1:00 PM) are selectable; accessible button semantics for overflow. Decision needed: scroll within day cell vs. expand-in-place vs. higher cap for companies with dense slot grids. Backend (attik-backend) GET schedule/optimal-slots in src/routes/schedule.ts supplies slots; no change required for this specific UI bug unless investigation shows 1:00 absent from API (unlikely per report). Out of scope (confirmed not the root cause) Missing API availability / service-area filtering as primary cause. Pay at Close and manual workorder payments. References Loom recording Calendar UI: attik-frontend/src/app/scheduler/slots/CalendarView.tsx Slot step: attik-frontend/src/app/scheduler/slots/SlotsStep.tsx Visible cap: attik-frontend/src/app/scheduler/constants.ts Slot API: attik-backend/src/routes/schedule.ts (/optimal-slots)
Linear 1 day ago
Main App
Planned
Online scheduler “+N more” doesn’t expand hidden time slots (Top Choice)
Objective Make the online scheduler calendar show all bookable times on busy days, not only the first five. Fix the “+N more” overflow control on the Choose Your Time Slot step so it actually expands (or is replaced with working UX). Unblock Top Choice Inspection Services testing and launch: 1:00 PM (and other times) are available on inspector schedules but hidden behind a non-functional “+2 more” label. Background Neil reported on the Top Choice online scheduler: at the time-picking step, a control that looks like an expander (“+2” / “+1”) does not respond when clicked. 1:00 PM availability is missing from the calendar even though Sean and David both have 1:00 on the internal schedule. Top Choice uses many custom start times, so a single day often has more than five distinct slot times from schedule/optimal-slots. Slots are returned by the API; the calendar truncates display to five per day and shows static “+N more” text with no click handler—users interpret it as broken. Repro walkthrough: Loom — Top Choice Online Scheduler Time Slot Not Showing Transcript summary (Neil): On Top Choice online scheduler, reached time selection. “Plus 2” / “plus 1” expander GUI does not work. 1:00 not shown; Sean and David both have 1:00 on the schedule. Expectation: clicking +2 / +1 should expand to show those times. Scope Frontend (attik-frontend) Public flow: /scheduler → Choose Your Time Slot in src/app/scheduler/slots/SlotsStep.tsx, rendered by src/app/scheduler/slots/CalendarView.tsx. src/app/scheduler/constants.ts sets MAX_VISIBLE_SLOTS_PER_DAY = 5; renderDayCell only maps slotsForDay.slice(0, MAX_VISIBLE_SLOTS_PER_DAY). When slotsForDay.length > MAX_VISIBLE_SLOTS_PER_DAY, UI renders a non-interactive label (+{count} more) around lines 694–697 in CalendarView.tsx—no expand state, no onClick / onPress. Slot times are grouped by exact datetime in slotsByDate; each custom start time counts toward the five-slot cap. SlotsStep passes autoSelectTopInspector={true}—separate from this bug: affects inspector picker on multi-inspector rows, not revealing hidden times. In scope: working expand/collapse per day (or equivalent) so overflow times (e.g. 1:00 PM) are selectable; accessible button semantics for overflow. Decision needed: scroll within day cell vs. expand-in-place vs. higher cap for companies with dense slot grids. Backend (attik-backend) GET schedule/optimal-slots in src/routes/schedule.ts supplies slots; no change required for this specific UI bug unless investigation shows 1:00 absent from API (unlikely per report). Out of scope (confirmed not the root cause) Missing API availability / service-area filtering as primary cause. Pay at Close and manual workorder payments. References Loom recording Calendar UI: attik-frontend/src/app/scheduler/slots/CalendarView.tsx Slot step: attik-frontend/src/app/scheduler/slots/SlotsStep.tsx Visible cap: attik-frontend/src/app/scheduler/constants.ts Slot API: attik-backend/src/routes/schedule.ts (/optimal-slots)
Linear 1 day ago
Main App
Planned
Error editing report features to remove ProPair Quotes
Updating the sample report to remove ProPair Quotes fails with a server components render error. The intended live features for the Sherwood launch are Repair Builder, Addendum, and Agent AI.
Linear 1 day ago
Main App
Planned
Error editing report features to remove ProPair Quotes
Updating the sample report to remove ProPair Quotes fails with a server components render error. The intended live features for the Sherwood launch are Repair Builder, Addendum, and Agent AI.
Linear 1 day ago
Main App
Planned
QuickBooks invoice total does not update after service removal
When a service is removed from an invoice in Attik, the corresponding QuickBooks invoice total does not adjust. Re-syncing in Attik does not update the QuickBooks invoice. Reported examples: TCI3398 and TCI3445.
Linear 2 days ago
Main App
Planned
QuickBooks invoice total does not update after service removal
When a service is removed from an invoice in Attik, the corresponding QuickBooks invoice total does not adjust. Re-syncing in Attik does not update the QuickBooks invoice. Reported examples: TCI3398 and TCI3445.
Linear 2 days ago
Main App
Planned
Reorder contacts in Attik mobile by role order
Objective On inspection detail in Attik mobile, the Contacts section should list people in the same order inspectors see on Attik web—driven by each role’s configured order (e.g. Clients → Client’s Agent → Listing Agent), not by how rows were added or synced into inspection.people. Align mobile with existing company contact-role settings so field staff can scan contacts in a predictable, role-based order. Background Product feedback (Christopher Scott): under Contacts on mobile, users expect contacts top-to-bottom as Clients, then Client’s Agent, then Listing Agent. The requirement is that display order matches the order field on populated roles in the people array—the same sort key companies configure under contact roles. Attik web already sorts inspection people by _roleId.order (with a high fallback when order is missing) in PeopleBox before rendering. Attik mobile currently passes inspection.people through unchanged; ContactsList maps the array in API/storage order, so Listing Agent or other roles can appear above Client or Client’s Agent even when role settings say otherwise. The inspection API already returns order on populated people._roleId; mobile types omit it but the value is available at runtime when people is populated. Scope Backend Contact roles define order on contactRolesSchema (attik-backend/src/models/contactRolesSchema.ts, default 9999). Inspection reads with populate: people include role order via people._roleId select in attik-backend/src/routes/inspection.ts (name, icon, description, portalPermissions, order). Aggregation population in attik-backend/src/util/functions/aggregation/inspectionPopulation.ts merges full role objects into people—no change required unless mobile populate paths differ. Frontend (reference behavior) attik-frontend/src/components/ui/PeopleBox.tsx is the parity target: roleDisplayOrder reads Number(role.order) with fallback 9999, comparePeopleForDisplay sorts people before grouping by role. Mobile should behave consistently with this pattern; exact reuse vs. local helper is a dev choice. Mobile Inspection detail in attik-mobile/app/(app)/inspection/[id].tsx builds people from inspection?.people and renders with no sort. attik-mobile/components/inspection/ContactsList.tsx maps people in array order. attik-mobile/types/index.ts — ContactRole currently has _id, name, type only; extending types for order may be needed for clarity. attik-mobile/hooks/useInspectionDetails.ts and useInspections.ts request populated people—data should include order when populated. Also in scope: the deduped uniquePeople list and client-portal dropdown options in [id].tsx are built from the same unsorted people loop; sorting for the Contacts section should keep portal option order aligned with the visible contact list. Out of scope unless discovered in QA: changing company role order values in settings; reordering rows inside inspection.people on the server. Decision needed Whether to sort in ContactsList only vs. a shared sorted people memo in [id].tsx (portal dropdown and any other consumers). Tie-break when two people share the same role order (web uses contact id string compare after role order). References Mobile: attik-mobile/app/(app)/inspection/[id].tsx, attik-mobile/components/inspection/ContactsList.tsx Web parity: attik-frontend/src/components/ui/PeopleBox.tsx API populate: attik-backend/src/routes/inspection.ts, attik-backend/src/models/contactRolesSchema.ts
Linear 2 days ago
Main App
Planned
Reorder contacts in Attik mobile by role order
Objective On inspection detail in Attik mobile, the Contacts section should list people in the same order inspectors see on Attik web—driven by each role’s configured order (e.g. Clients → Client’s Agent → Listing Agent), not by how rows were added or synced into inspection.people. Align mobile with existing company contact-role settings so field staff can scan contacts in a predictable, role-based order. Background Product feedback (Christopher Scott): under Contacts on mobile, users expect contacts top-to-bottom as Clients, then Client’s Agent, then Listing Agent. The requirement is that display order matches the order field on populated roles in the people array—the same sort key companies configure under contact roles. Attik web already sorts inspection people by _roleId.order (with a high fallback when order is missing) in PeopleBox before rendering. Attik mobile currently passes inspection.people through unchanged; ContactsList maps the array in API/storage order, so Listing Agent or other roles can appear above Client or Client’s Agent even when role settings say otherwise. The inspection API already returns order on populated people._roleId; mobile types omit it but the value is available at runtime when people is populated. Scope Backend Contact roles define order on contactRolesSchema (attik-backend/src/models/contactRolesSchema.ts, default 9999). Inspection reads with populate: people include role order via people._roleId select in attik-backend/src/routes/inspection.ts (name, icon, description, portalPermissions, order). Aggregation population in attik-backend/src/util/functions/aggregation/inspectionPopulation.ts merges full role objects into people—no change required unless mobile populate paths differ. Frontend (reference behavior) attik-frontend/src/components/ui/PeopleBox.tsx is the parity target: roleDisplayOrder reads Number(role.order) with fallback 9999, comparePeopleForDisplay sorts people before grouping by role. Mobile should behave consistently with this pattern; exact reuse vs. local helper is a dev choice. Mobile Inspection detail in attik-mobile/app/(app)/inspection/[id].tsx builds people from inspection?.people and renders with no sort. attik-mobile/components/inspection/ContactsList.tsx maps people in array order. attik-mobile/types/index.ts — ContactRole currently has _id, name, type only; extending types for order may be needed for clarity. attik-mobile/hooks/useInspectionDetails.ts and useInspections.ts request populated people—data should include order when populated. Also in scope: the deduped uniquePeople list and client-portal dropdown options in [id].tsx are built from the same unsorted people loop; sorting for the Contacts section should keep portal option order aligned with the visible contact list. Out of scope unless discovered in QA: changing company role order values in settings; reordering rows inside inspection.people on the server. Decision needed Whether to sort in ContactsList only vs. a shared sorted people memo in [id].tsx (portal dropdown and any other consumers). Tie-break when two people share the same role order (web uses contact id string compare after role order). References Mobile: attik-mobile/app/(app)/inspection/[id].tsx, attik-mobile/components/inspection/ContactsList.tsx Web parity: attik-frontend/src/components/ui/PeopleBox.tsx API populate: attik-backend/src/routes/inspection.ts, attik-backend/src/models/contactRolesSchema.ts
Linear 2 days ago
Main App
Planned
Imported ISN contacts have contact preferences disabled
Contacts imported from ISN appear to have both email and SMS contact preferences toggled off, which is preventing agents from receiving reports. The immediate workaround is to update them manually, but the historical import likely needs a code fix and there may also need to be a programmatic way to enable these preferences.
Linear 3 days ago
Main App
Planned
Imported ISN contacts have contact preferences disabled
Contacts imported from ISN appear to have both email and SMS contact preferences toggled off, which is preventing agents from receiving reports. The immediate workaround is to update them manually, but the historical import likely needs a code fix and there may also need to be a programmatic way to enable these preferences.
Linear 3 days ago
Main App
Planned
Surface activity feed for all event types
System logs for events should be viewable in an activity feed across all event types.
Linear 3 days ago
Main App
Planned
Surface activity feed for all event types
System logs for events should be viewable in an activity feed across all event types.
Linear 3 days ago
Main App
Planned
Add equipment display order sorting options
Feature request for alphanumeric sorting (A–Z and Z–A) of equipment by serial number, equipment title, and calibration date. Category-specific sorting would be preferred, but page-wide sorting across all categories would also work. If sorting is not feasible, a manual reordering control such as click-and-drag for each equipment item would be an acceptable alternative.
Linear 3 days ago
Main App
Planned
Add equipment display order sorting options
Feature request for alphanumeric sorting (A–Z and Z–A) of equipment by serial number, equipment title, and calibration date. Category-specific sorting would be preferred, but page-wide sorting across all categories would also work. If sorting is not feasible, a manual reordering control such as click-and-drag for each equipment item would be an acceptable alternative.
Linear 3 days ago
Main App
Planned
Warn when client and agent contact details match
The scheduler should warn users when the client and agent appear to be the same person, so staff do not accidentally create orders where the agent is entered as the client. Transcript Context On the May 27, 2026 SE Attik call, the team described repeated cases where agents entered themselves as both the client and the agent. This led to bad downstream data on agreements and reports and created confusion about who the legal purchaser actually was. The suggested direction was to detect when the client and agent contact details match and prompt the user to confirm that they are actually the purchaser, rather than allowing the workflow to proceed silently. The group emphasized that agents may think they are protecting the client from emails, but the result is legally and operationally incorrect data.
Linear 3 days ago
Main App
Planned
Warn when client and agent contact details match
The scheduler should warn users when the client and agent appear to be the same person, so staff do not accidentally create orders where the agent is entered as the client. Transcript Context On the May 27, 2026 SE Attik call, the team described repeated cases where agents entered themselves as both the client and the agent. This led to bad downstream data on agreements and reports and created confusion about who the legal purchaser actually was. The suggested direction was to detect when the client and agent contact details match and prompt the user to confirm that they are actually the purchaser, rather than allowing the workflow to proceed silently. The group emphasized that agents may think they are protecting the client from emails, but the result is legally and operationally incorrect data.
Linear 3 days ago
Main App
Planned
PDF report can retain wrong client name after contact correction
Generated PDF reports can continue showing the wrong person’s name even after the order or contact information has been corrected. Transcript Context On the May 27, 2026 SE Attik call, the team discussed a case where the PDF output was still displaying the wrong name even though the record had been corrected. Ryan noted the bad value only appeared on the downloaded PDF output and said the immediate workaround would be to manually edit the PDF for the client. The group believes the issue may stem from an earlier client-name change flowing into the PDF output and not updating everywhere it should. This creates downstream confusion because the visible PDF can remain wrong even after the source order has been fixed.
Linear 3 days ago
Main App
Planned
PDF report can retain wrong client name after contact correction
Generated PDF reports can continue showing the wrong person’s name even after the order or contact information has been corrected. Transcript Context On the May 27, 2026 SE Attik call, the team discussed a case where the PDF output was still displaying the wrong name even though the record had been corrected. Ryan noted the bad value only appeared on the downloaded PDF output and said the immediate workaround would be to manually edit the PDF for the client. The group believes the issue may stem from an earlier client-name change flowing into the PDF output and not updating everywhere it should. This creates downstream confusion because the visible PDF can remain wrong even after the source order has been fixed.
Linear 3 days ago
Main App
Planned
Inspector pictures not loading in meet your inspector email
Inspector pictures are not loading in the "meet your inspector" email. The images were loading earlier this week, and several emails were tested with the same result.
Linear 3 days ago
Main App
Planned
Inspector pictures not loading in meet your inspector email
Inspector pictures are not loading in the "meet your inspector" email. The images were loading earlier this week, and several emails were tested with the same result.
Linear 3 days ago
Main App
Planned
Dropdown menus truncate long list object text
In dropdown/select menus for list objects, the full text string is not always visible and items can appear cut off. In the List Workflow modal, this occurs when adding a condition such as Services → contains and opening the value dropdown. The full text becomes visible after widening the window and reopening the modal, which suggests the dropdown width is not adapting correctly in narrower layouts. Screen Recording 2026-05-27 at 2.38.38 PM.mp4
Linear 3 days ago
Main App
Planned
Dropdown menus truncate long list object text
In dropdown/select menus for list objects, the full text string is not always visible and items can appear cut off. In the List Workflow modal, this occurs when adding a condition such as Services → contains and opening the value dropdown. The full text becomes visible after widening the window and reopening the modal, which suggests the dropdown width is not adapting correctly in narrower layouts. Screen Recording 2026-05-27 at 2.38.38 PM.mp4
Linear 3 days ago
Main App
Planned
Duplicate email error when updating PM profile
Updating a PM profile email from prentsmanager@gmail.com to charliemeyer5280@gmail.com fails on save with a duplicate key error. There does not appear to be a duplicate profile with that email. Another profile exists with prentsdenver@outlook.com, but merge is not working. There is also a Spectora profile under Charles Meyer that has not been added over.
Linear 3 days ago
Main App
Planned
Duplicate email error when updating PM profile
Updating a PM profile email from prentsmanager@gmail.com to charliemeyer5280@gmail.com fails on save with a duplicate key error. There does not appear to be a duplicate profile with that email. Another profile exists with prentsdenver@outlook.com, but merge is not working. There is also a Spectora profile under Charles Meyer that has not been added over.
Linear 3 days ago
Main App
Planned
Unified workspace: one tools shell across selected instances
Objective Let office staff (and similar internal roles) with memberships in multiple Attik instances work in one tools shell instead of switching company and reloading for every task. Support a user-defined workspace: a home instance for branding/navigation plus a selected set of included instances where the user’s permissioned reads (and eventually writes) are available in one place. Reduce coordinator friction for multi-brand operations (e.g. Florida / Monument-style groups) while preserving per-instance data isolation and permission boundaries. Background Today every tools session has a single active company (selectedCompany on the auth session, updated via POST /auth/web/switch-company in webAuthEndpoints.ts). Switching company in the UI calls switchCompanyAction and triggers a full page reload (NavUser.tsx). Nearly all tools APIs scope data with res.locals.company._id (calendar, schedule, quotes, inspections, events). Cross-company behavior exists only in narrow admin paths (e.g. cc-dashboard with getAllowedCompanyIds, GET /company/my-companies, Client Care Dashboard). The same user can hold multiple memberships (membershipSchema.ts) with different roles and permissions per _companyId; copyMembershipToCompanies.ts provisions separate employee records per company but does not unify the tools experience. This epic is separate from ATT-1714 (cross-instance inspector calendar blocking) and ATT-1763 (cross-instance quote handoff from workorder). Those solve scheduling availability and job→quote transfer; this epic solves coordinator UX and federated access across instances. Child issues Implement in dependency order via blockedBy: (created below) — Workspace model + session extension (created below) — Workspace configuration UI (Admin and/or account settings) (created below) — Cross-company authorization helpers (created below) — Federated calendar view (created below) — Federated worklist / tasks / inspections lists (created below) — Navigation shell without full reload on context change (created below) — Write paths and explicit create targeting Product decisions (proposed — confirm during implementation) Audience (v1): Office staff / coordinators with multiple active memberships; decision needed whether inspector tools roles are in scope. Workspace: homeCompanyId + includedCompanyIds[] ⊆ user’s active memberships. Permissions: Check against the resource’s _companyId membership (not a naive union of all permissions). Creates (v1): Always target one _companyId (explicit picker or row context); no ambiguous cross-company writes. Phasing: Prefer read federated first (calendar + high-traffic lists), then expand writes. Security: Optional client companyIds must always be intersected with allowed memberships (pattern from ccDashboard.ts). Scope (epic-level) Coordinates session, authorization, federated APIs, and tools UI. Detail lives on child issues. Key touchpoints: Session / switch: attik-backend/src/util/functions/betterAuth/web/webAuthEndpoints.ts, attik-frontend/src/actions/switchCompany.ts Memberships: attik-backend/src/models/membershipSchema.ts, attik-frontend/src/components/navbar/NavUser.tsx Multi-company precedent: attik-backend/src/routes/ccDashboard.ts, attik-backend/src/routes/company.ts (/my-companies), attik-frontend/src/app/admin/client-care-dashboard/ Calendar: attik-frontend/src/app/tools/calendar/MasterCalendar.tsx, attik-backend/src/routes/calendar.ts, attik-backend/src/routes/schedule.ts Out of scope: Merging tenant data; replacing instance-management; implementing ATT-1714 or ATT-1763 (integrate when those ship). References Related: ATT-1714 — Cross-instance shared calendars Related: ATT-1763 — Create quote in another instance from existing job Diagram flowchart TB subgraph today [Today] S[Session selectedCompany] T[Tools UI single company] API[APIs filter res.locals.company] S --> T --> API end subgraph target [Unified workspace] W[Workspace home + included companies] F[Federated read APIs] UI[One shell with company chips on rows] W --> F --> UI end today -.-> target
Linear 4 days ago
Main App
Planned
Unified workspace: one tools shell across selected instances
Objective Let office staff (and similar internal roles) with memberships in multiple Attik instances work in one tools shell instead of switching company and reloading for every task. Support a user-defined workspace: a home instance for branding/navigation plus a selected set of included instances where the user’s permissioned reads (and eventually writes) are available in one place. Reduce coordinator friction for multi-brand operations (e.g. Florida / Monument-style groups) while preserving per-instance data isolation and permission boundaries. Background Today every tools session has a single active company (selectedCompany on the auth session, updated via POST /auth/web/switch-company in webAuthEndpoints.ts). Switching company in the UI calls switchCompanyAction and triggers a full page reload (NavUser.tsx). Nearly all tools APIs scope data with res.locals.company._id (calendar, schedule, quotes, inspections, events). Cross-company behavior exists only in narrow admin paths (e.g. cc-dashboard with getAllowedCompanyIds, GET /company/my-companies, Client Care Dashboard). The same user can hold multiple memberships (membershipSchema.ts) with different roles and permissions per _companyId; copyMembershipToCompanies.ts provisions separate employee records per company but does not unify the tools experience. This epic is separate from ATT-1714 (cross-instance inspector calendar blocking) and ATT-1763 (cross-instance quote handoff from workorder). Those solve scheduling availability and job→quote transfer; this epic solves coordinator UX and federated access across instances. Child issues Implement in dependency order via blockedBy: (created below) — Workspace model + session extension (created below) — Workspace configuration UI (Admin and/or account settings) (created below) — Cross-company authorization helpers (created below) — Federated calendar view (created below) — Federated worklist / tasks / inspections lists (created below) — Navigation shell without full reload on context change (created below) — Write paths and explicit create targeting Product decisions (proposed — confirm during implementation) Audience (v1): Office staff / coordinators with multiple active memberships; decision needed whether inspector tools roles are in scope. Workspace: homeCompanyId + includedCompanyIds[] ⊆ user’s active memberships. Permissions: Check against the resource’s _companyId membership (not a naive union of all permissions). Creates (v1): Always target one _companyId (explicit picker or row context); no ambiguous cross-company writes. Phasing: Prefer read federated first (calendar + high-traffic lists), then expand writes. Security: Optional client companyIds must always be intersected with allowed memberships (pattern from ccDashboard.ts). Scope (epic-level) Coordinates session, authorization, federated APIs, and tools UI. Detail lives on child issues. Key touchpoints: Session / switch: attik-backend/src/util/functions/betterAuth/web/webAuthEndpoints.ts, attik-frontend/src/actions/switchCompany.ts Memberships: attik-backend/src/models/membershipSchema.ts, attik-frontend/src/components/navbar/NavUser.tsx Multi-company precedent: attik-backend/src/routes/ccDashboard.ts, attik-backend/src/routes/company.ts (/my-companies), attik-frontend/src/app/admin/client-care-dashboard/ Calendar: attik-frontend/src/app/tools/calendar/MasterCalendar.tsx, attik-backend/src/routes/calendar.ts, attik-backend/src/routes/schedule.ts Out of scope: Merging tenant data; replacing instance-management; implementing ATT-1714 or ATT-1763 (integrate when those ship). References Related: ATT-1714 — Cross-instance shared calendars Related: ATT-1763 — Create quote in another instance from existing job Diagram flowchart TB subgraph today [Today] S[Session selectedCompany] T[Tools UI single company] API[APIs filter res.locals.company] S --> T --> API end subgraph target [Unified workspace] W[Workspace home + included companies] F[Federated read APIs] UI[One shell with company chips on rows] W --> F --> UI end today -.-> target
Linear 4 days ago
Main App
Planned
Link report and test-result context on cross-instance quotes
Objective Carry report or test-result context from the source job into the cross-instance handoff so destination CSRs can prepare proposals (e.g. radon mitigation) without hunting the source inspection. Background May 26 call called out supporting report/test-result context when helpful for the proposal. Source reports are tied to inspections via workorder reports UI (attik-frontend/src/app/tools/inspections/[id]/ reports section, client report URLs). Cross-instance report access may require links rather than file copy — decision needed. Scope Backend Decision needed: link-only (URL + metadata on quote or note) vs copy report artifacts into destination company storage. If link-only: persist source inspection slug/id, company id, and report identifiers readable by users with access to source instance. If copy: define allowed report types and permissions; likely larger scope. Optional note on destination quote (schedulerNotes / message on quoteSchema) with structured source reference. Frontend In workorder handoff flow: optional step or checkbox to include report context (list available published reports on source inspection). On destination pending quote: display link(s) back to source report(s) and/or attached summary for CSR. Out of scope Creating unconfirmed inspections in destination. Full report hub cross-instance permissions redesign. References attik-frontend/src/app/tools/inspections/[id]/components/reports/ (report actions on workorder) attik-backend/src/models/quoteSchema.ts (message, schedulerNotes)
Linear 4 days ago
Main App
Planned
Link report and test-result context on cross-instance quotes
Objective Carry report or test-result context from the source job into the cross-instance handoff so destination CSRs can prepare proposals (e.g. radon mitigation) without hunting the source inspection. Background May 26 call called out supporting report/test-result context when helpful for the proposal. Source reports are tied to inspections via workorder reports UI (attik-frontend/src/app/tools/inspections/[id]/ reports section, client report URLs). Cross-instance report access may require links rather than file copy — decision needed. Scope Backend Decision needed: link-only (URL + metadata on quote or note) vs copy report artifacts into destination company storage. If link-only: persist source inspection slug/id, company id, and report identifiers readable by users with access to source instance. If copy: define allowed report types and permissions; likely larger scope. Optional note on destination quote (schedulerNotes / message on quoteSchema) with structured source reference. Frontend In workorder handoff flow: optional step or checkbox to include report context (list available published reports on source inspection). On destination pending quote: display link(s) back to source report(s) and/or attached summary for CSR. Out of scope Creating unconfirmed inspections in destination. Full report hub cross-instance permissions redesign. References attik-frontend/src/app/tools/inspections/[id]/components/reports/ (report actions on workorder) attik-backend/src/models/quoteSchema.ts (message, schedulerNotes)
Linear 4 days ago
Main App
Planned
Cross-instance service and charge mapping for destination quotes
Objective When handing off a job across instances, help CSRs map source inspection charges to destination company services so the new pending quote includes meaningful line items instead of an empty or manual-only quote. Background Parent epic: pending quote in destination from source inspection. Service catalogs differ by company/brand; source charge _serviceId values are not valid on destination quotes. Source charges live on inspection population and chargeSchema; destination quote charges are created via createOrUpdateCharges inside createQuoteWithCharges. Workorder UI child provides the handoff entry point; API child creates the quote shell. Scope Backend Preview or mapping endpoint: given source inspection + targetCompanyId, return source charges (name, category, amount) and suggested destination services (by name/category match — decision needed on matching rules). Accept user-selected service mapping (source charge or service id → destination service id) on create or follow-up PATCH to quote. Create destination charges with correct destination _serviceId and pricing rules for target company (decision needed: copy amounts vs recalculate via destination price engine). Do not copy source Mongo charge ids onto destination quote. Frontend In workorder handoff flow (after destination selected): show source line items and destination service picker or suggested matches. Allow proceed with partial mapping; clearly label unmapped items that CSR must add in destination scheduler. Out of scope Unconfirmed inspection creation. Spectora service id sync across companies. References attik-backend/src/util/functions/quote/createQuoteWithCharges.ts attik-backend/src/models/chargeSchema.ts attik-backend/src/models/serviceSchema.ts (services catalog)
Linear 4 days ago
Main App
Planned
Cross-instance service and charge mapping for destination quotes
Objective When handing off a job across instances, help CSRs map source inspection charges to destination company services so the new pending quote includes meaningful line items instead of an empty or manual-only quote. Background Parent epic: pending quote in destination from source inspection. Service catalogs differ by company/brand; source charge _serviceId values are not valid on destination quotes. Source charges live on inspection population and chargeSchema; destination quote charges are created via createOrUpdateCharges inside createQuoteWithCharges. Workorder UI child provides the handoff entry point; API child creates the quote shell. Scope Backend Preview or mapping endpoint: given source inspection + targetCompanyId, return source charges (name, category, amount) and suggested destination services (by name/category match — decision needed on matching rules). Accept user-selected service mapping (source charge or service id → destination service id) on create or follow-up PATCH to quote. Create destination charges with correct destination _serviceId and pricing rules for target company (decision needed: copy amounts vs recalculate via destination price engine). Do not copy source Mongo charge ids onto destination quote. Frontend In workorder handoff flow (after destination selected): show source line items and destination service picker or suggested matches. Allow proceed with partial mapping; clearly label unmapped items that CSR must add in destination scheduler. Out of scope Unconfirmed inspection creation. Spectora service id sync across companies. References attik-backend/src/util/functions/quote/createQuoteWithCharges.ts attik-backend/src/models/chargeSchema.ts attik-backend/src/models/serviceSchema.ts (services catalog)
Linear 4 days ago
Main App
Planned
Cross-instance contact match and role mapping
Objective Ensure contacts on cross-instance quotes are matched or created in the destination company with correct contact roles, including user-visible mapping when roles do not align across brands. Background Parent epic: pending quote in destination from source inspection. API child creates people[] on the quote; this child deepens role mapping and UX for mismatches. Action flow migration already solves cross-company contact role and required info mapping in ActionFlowMigrationClient.tsx with backend support in contactRoleFlowMigration.ts and copyActionFlowToCompany.ts — reuse patterns where possible, scoped to quote handoff. Scope Backend Extend cross-instance handoff to accept optional contactRoleMapping (source role id → target role id), similar to flow.ts copy body shape. Suggest target roles for unmapped source roles (endpoint or included in preview response — decision needed). Match-or-create contacts by email/phone; never persist source _contactId on destination quote. Handle primary contact flag and multiple people on source inspection. Frontend When source and target roles differ, present mapping UI before create (or on preview step) — patterns from attik-frontend/src/app/admin/settings/instance-management/action-flow-migration/ActionFlowMigrationClient.tsx (crSuggestions, crUserMapping). Surface unmapped roles clearly; block submit until required mappings resolved or decision needed for optional roles. Out of scope Service/charge mapping. Report attachments. References attik-backend/src/util/functions/actionFlows/contactRoleFlowMigration.ts attik-backend/src/util/functions/spectora/findSpectoraContactMatches.ts attik-frontend/src/app/admin/settings/instance-management/action-flow-migration/ActionFlowMigrationClient.tsx
Linear 4 days ago
Main App
Planned
Cross-instance contact match and role mapping
Objective Ensure contacts on cross-instance quotes are matched or created in the destination company with correct contact roles, including user-visible mapping when roles do not align across brands. Background Parent epic: pending quote in destination from source inspection. API child creates people[] on the quote; this child deepens role mapping and UX for mismatches. Action flow migration already solves cross-company contact role and required info mapping in ActionFlowMigrationClient.tsx with backend support in contactRoleFlowMigration.ts and copyActionFlowToCompany.ts — reuse patterns where possible, scoped to quote handoff. Scope Backend Extend cross-instance handoff to accept optional contactRoleMapping (source role id → target role id), similar to flow.ts copy body shape. Suggest target roles for unmapped source roles (endpoint or included in preview response — decision needed). Match-or-create contacts by email/phone; never persist source _contactId on destination quote. Handle primary contact flag and multiple people on source inspection. Frontend When source and target roles differ, present mapping UI before create (or on preview step) — patterns from attik-frontend/src/app/admin/settings/instance-management/action-flow-migration/ActionFlowMigrationClient.tsx (crSuggestions, crUserMapping). Surface unmapped roles clearly; block submit until required mappings resolved or decision needed for optional roles. Out of scope Service/charge mapping. Report attachments. References attik-backend/src/util/functions/actionFlows/contactRoleFlowMigration.ts attik-backend/src/util/functions/spectora/findSpectoraContactMatches.ts attik-frontend/src/app/admin/settings/instance-management/action-flow-migration/ActionFlowMigrationClient.tsx
Linear 4 days ago
Main App
Planned
Workorder UI: create quote in another instance
Objective Add a workorder action so CSRs can pick a destination instance and create a pending quote from the current inspection with clear confirmation of what will be copied. Navigate the user to the new quote (or quote workflow) in the destination instance after success. Background Preferred UX is inspection workorder only (not admin instance management). Depends on cross-instance create API (sibling child: pending quote from source inspection). Workorder shell and actions live under attik-frontend/src/app/tools/inspections/[id]/ (InspectionWorkorderShell.tsx, WorkorderActionsDropdown.tsx). Users switch companies today via membership / switchCompany in attik-frontend/src/actions/switchCompany.ts; target picker should list only companies the user can access. Scope Frontend New action on workorder (e.g. in WorkorderActionsDropdown.tsx or equivalent): Create quote in another instance… Modal or drawer flow: Destination company selector (memberships user shares with source). Summary of what will copy (property, contacts) and what will not (services until mapping child, integrations, source job link TBD). Call cross-instance API; handle errors (no permission, no membership). Success: navigate to destination pending quote (quote detail, task list, or scheduler entry point — decision needed). Show source context on success or on destination quote when provenance fields exist (source company name, inspection id/slug). Respect permission gate aligned with API child (dual membership + dedicated permission). Backend None beyond API contract from sibling child; optional small GET for eligible target companies if not inferred from session memberships. Out of scope Admin instance-management entry point. Contact role mapping UI (separate child). Service mapping and report attachment (separate children). References attik-frontend/src/app/tools/inspections/[id]/components/WorkorderActionsDropdown.tsx attik-frontend/src/app/tools/inspections/[id]/components/InspectionWorkorderShell.tsx attik-frontend/src/actions/switchCompany.ts attik-frontend/src/components/task-check/SingleQuoteBar.tsx (quote navigation patterns)
Linear 4 days ago
Main App
Planned
Workorder UI: create quote in another instance
Objective Add a workorder action so CSRs can pick a destination instance and create a pending quote from the current inspection with clear confirmation of what will be copied. Navigate the user to the new quote (or quote workflow) in the destination instance after success. Background Preferred UX is inspection workorder only (not admin instance management). Depends on cross-instance create API (sibling child: pending quote from source inspection). Workorder shell and actions live under attik-frontend/src/app/tools/inspections/[id]/ (InspectionWorkorderShell.tsx, WorkorderActionsDropdown.tsx). Users switch companies today via membership / switchCompany in attik-frontend/src/actions/switchCompany.ts; target picker should list only companies the user can access. Scope Frontend New action on workorder (e.g. in WorkorderActionsDropdown.tsx or equivalent): Create quote in another instance… Modal or drawer flow: Destination company selector (memberships user shares with source). Summary of what will copy (property, contacts) and what will not (services until mapping child, integrations, source job link TBD). Call cross-instance API; handle errors (no permission, no membership). Success: navigate to destination pending quote (quote detail, task list, or scheduler entry point — decision needed). Show source context on success or on destination quote when provenance fields exist (source company name, inspection id/slug). Respect permission gate aligned with API child (dual membership + dedicated permission). Backend None beyond API contract from sibling child; optional small GET for eligible target companies if not inferred from session memberships. Out of scope Admin instance-management entry point. Contact role mapping UI (separate child). Service mapping and report attachment (separate children). References attik-frontend/src/app/tools/inspections/[id]/components/WorkorderActionsDropdown.tsx attik-frontend/src/app/tools/inspections/[id]/components/InspectionWorkorderShell.tsx attik-frontend/src/actions/switchCompany.ts attik-frontend/src/components/task-check/SingleQuoteBar.tsx (quote navigation patterns)
Linear 4 days ago
Main App
Planned
Cross-instance API: create pending quote from source inspection
Objective Provide a backend API that creates a pending quote in a target company from a source inspection, carrying property and contact data without cross-company ID leakage. Enforce dual-membership and a dedicated permission so only authorized users can hand off jobs across instances. Background Parent epic: cross-instance quote from existing job (workorder entry point). Quotes are created per company via createQuoteWithCharges in attik-backend/src/util/functions/quote/createQuoteWithCharges.ts with _companyId on quoteSchema. No existing route creates a quote in company B from an inspection in company A. Cross-instance action flow copy (copyActionFlowToCompany in attik-backend/src/util/functions/actionFlows/copyActionFlowToCompany.ts, endpoints in attik-backend/src/routes/flow.ts) is the closest pattern for targetCompanyId + allowed-company checks. Scope Backend New endpoint (e.g. under instance-management or inspection/:id/...) accepting source inspection id + targetCompanyId; validate user membership in both companies and new permission. Load populated source inspection (property, people, charges as reference); build quote payload for target _companyId. Create quote with status: 'pending' via createQuoteWithCharges (or equivalent transaction used by attik-backend/src/routes/quote.ts). Contacts: match-or-create in target company (patterns similar to findSpectoraContactMatches in attik-backend/src/util/functions/spectora/findSpectoraContactMatches.ts); map people[] with destination _contactId / _roleId only. Property: copy address/property fields onto destination quote property. Provenance: decision needed — optional fields on quote for _sourceInspectionId, _sourceCompanyId, created-by metadata for audit. Exclude from copy: wcLeadId, _hubspotDealId, _finalInspectionId, source charge/service Mongo IDs, source inspector IDs. Services/charges: initial create may omit charges or include empty charge list until service-mapping child lands; decision needed on minimum viable payload. Return created quote id/slug and target company id for frontend navigation. Frontend None in this child (workorder UI is a separate child); expose types/server action contract as needed for the UI child. Out of scope Workorder modal UX. Contact role mapping UI (separate child). Service mapping, report attachments. Creating unconfirmed inspections in destination. References attik-backend/src/util/functions/quote/createQuoteWithCharges.ts attik-backend/src/routes/quote.ts attik-backend/src/models/quoteSchema.ts attik-backend/src/routes/protectedRouteBarrel.ts (/instance-management) attik-backend/src/routes/flow.ts (cross-company copy guards)
Linear 4 days ago
Main App
Planned
Cross-instance API: create pending quote from source inspection
Objective Provide a backend API that creates a pending quote in a target company from a source inspection, carrying property and contact data without cross-company ID leakage. Enforce dual-membership and a dedicated permission so only authorized users can hand off jobs across instances. Background Parent epic: cross-instance quote from existing job (workorder entry point). Quotes are created per company via createQuoteWithCharges in attik-backend/src/util/functions/quote/createQuoteWithCharges.ts with _companyId on quoteSchema. No existing route creates a quote in company B from an inspection in company A. Cross-instance action flow copy (copyActionFlowToCompany in attik-backend/src/util/functions/actionFlows/copyActionFlowToCompany.ts, endpoints in attik-backend/src/routes/flow.ts) is the closest pattern for targetCompanyId + allowed-company checks. Scope Backend New endpoint (e.g. under instance-management or inspection/:id/...) accepting source inspection id + targetCompanyId; validate user membership in both companies and new permission. Load populated source inspection (property, people, charges as reference); build quote payload for target _companyId. Create quote with status: 'pending' via createQuoteWithCharges (or equivalent transaction used by attik-backend/src/routes/quote.ts). Contacts: match-or-create in target company (patterns similar to findSpectoraContactMatches in attik-backend/src/util/functions/spectora/findSpectoraContactMatches.ts); map people[] with destination _contactId / _roleId only. Property: copy address/property fields onto destination quote property. Provenance: decision needed — optional fields on quote for _sourceInspectionId, _sourceCompanyId, created-by metadata for audit. Exclude from copy: wcLeadId, _hubspotDealId, _finalInspectionId, source charge/service Mongo IDs, source inspector IDs. Services/charges: initial create may omit charges or include empty charge list until service-mapping child lands; decision needed on minimum viable payload. Return created quote id/slug and target company id for frontend navigation. Frontend None in this child (workorder UI is a separate child); expose types/server action contract as needed for the UI child. Out of scope Workorder modal UX. Contact role mapping UI (separate child). Service mapping, report attachments. Creating unconfirmed inspections in destination. References attik-backend/src/util/functions/quote/createQuoteWithCharges.ts attik-backend/src/routes/quote.ts attik-backend/src/models/quoteSchema.ts attik-backend/src/routes/protectedRouteBarrel.ts (/instance-management) attik-backend/src/routes/flow.ts (cross-company copy guards)
Linear 4 days ago
Main App