Multi-word search now uses Array.every so word order doesn't matter.
Scroll is reset to top after each filter so visible items aren't hidden
off-screen. A forced layout reflow (offsetHeight read) ensures iOS Safari
re-registers filtered-in elements as interactive — without it, items that
transition from display:none back to visible remain unresponsive to taps.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Progress widget dismiss now POSTs to Dashboard/DismissProgressWidget, writing
GuidedActivationDismissedAt to the DB so the widget stays hidden across devices
and cache clears (localStorage alone wasn't enough). BuildShopProgressWidgetAsync
suppresses the widget server-side when AllDone + dismissed.
New SuperAdmin page at /OnboardingProgress shows the activation funnel across
all tenant companies: wizard status, chosen path, milestone progress bar, key
dates (first job/quote, first invoice, workflow completed, widget dismissed),
and a status badge (Not Started / In Progress / Complete / Dismissed). Nav link
added under Users & Activity in the Platform Management sidebar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows an × button in the top-right of the completion card so users can
permanently hide the widget once they've seen the success message. Dismissal
is stored in localStorage (same pattern as the collapse state) so it persists
across page loads without requiring a DB migration. The widget hides itself
on the next load before any layout is shown, avoiding a flash.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous AnyAsync check used global query filters which hide
soft-deleted records. Deleting a lookup sets UpdatedAt on the record
(EF interceptor stamps Modified entities) but the IsDeleted filter
made it invisible to the query. Added ignoreQueryFilters: true with
an explicit CompanyId predicate so soft-deleted lookups are included —
any deletion or edit now correctly marks the step complete.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Injects ISubscriptionService into DashboardController and calls
GetUserCountAsync to check the plan's MaxUsers limit. When MaxUsers == 1
the "Bring your crew in" step is omitted from the progress widget entirely,
so solo-plan users aren't prompted to do something their subscription
doesn't allow. Plans with MaxUsers > 1 or unlimited (-1) show the step
as before.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GettingStarted.cshtml: updated Setup Wizard description from 10-step to
5-step, revised time estimate (5–10 min), added new "After the Wizard"
section explaining the guided activation first-workflow flow and the
progress widget (what it tracks, how the highlight works, when it
disappears, Admin-only visibility).
HelpKnowledgeBase.cs (AI assistant): updated Setup Wizard entry to 5 steps,
replaced old step list (removed steps 5–8, 10), updated new company
quick-start checklist to reference the 5-step wizard and the post-wizard
progress widget. Added new GUIDED ACTIVATION section covering the two
onboarding paths (Quote First / Job First), the Daily Board intro experience,
and how the banner auto-dismisses. Updated Common Workflows to reflect the
new onboarding order. Dashboard section now describes the progress widget
and its six tracked steps.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Setup Wizard: reduced from 10 steps to 5 (Company Info → QB Migration →
Pricing Defaults → Named Ovens → Notifications). Removed Doc Numbering,
Job Settings, Payment Terms, Pricing Tiers, and Team Members steps — these
all have sensible defaults and are accessible any time in Company Settings.
Wizard now completes in ~5 minutes instead of 15–20.
Dashboard progress widget (new): "Get the most out of your shop" checklist
appears for Company Admins after wizard completion. Tracks six post-setup
activation tasks with dynamic progress badge, motivating subtitle copy,
collapsed-state persistence via localStorage, and a full completion state
("Your shop is fully set up 🎉") that replaces the checklist at 100%.
The next recommended step is highlighted with a solid CTA button and a
subtle blue row tint. Completed steps show encouraging green subtext instead
of just "Done". Widget disappears from controller when AllDone would have
caused a silent vanish — now renders the completion state instead.
Guided activation (Daily Board): rewrote the BoardIntroStep callout to lead
with "This is your shop in real time" and a plain-English description of the
board's purpose. Added a separate InstructionText field to
GuidedActivationCalloutViewModel so the "Move this job to the next stage"
action prompt renders as a distinct bold line with an arrow icon rather than
being buried in the body copy. After the stage change, the confirmation
callout now reads "Nice — your workflow just updated" to reinforce what just
happened before prompting the invoice step.
All copy passes the "shop owner, not SaaS" test: no technical jargon,
benefit-driven descriptions, natural language throughout.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 3 — eliminated ApplicationDbContext from all non-exempt controllers,
routing all data access through IUnitOfWork. Added IPlainRepository<T> for
the four platform entities (Announcement, BannedIp, DashboardTip, ReleaseNote)
that intentionally don't extend BaseEntity and therefore can't use the
constrained IRepository<T>. Added permanent-exception comments to the 18
controllers that legitimately retain direct DbContext access (Identity infra,
cross-tenant platform ops, bulk streaming exports).
Phase 4 — added EnforceDataAccessArchitecture() to Program.cs, a startup
gate that reflects over every Controller subclass and throws at boot if any
non-exempt controller injects ApplicationDbContext. The app cannot start with
a violation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Migrated InvoicesController, QuotesController, JobsController, BillsController,
PurchaseOrdersController, and CustomersController to route all data access
through IUnitOfWork typed/generic repositories instead of injecting
ApplicationDbContext directly.
New typed repositories added: IJobRepository (GetScheduledJobsForDateAsync,
GetActiveJobsForMobileAsync, LoadForCostingAsync), INotificationLogRepository
(GetLatestForJobAsync, GetAllForJobAsync), IQuoteRepository (GetItemsWithCoatsAsync
with CatalogItem eager load + AsNoTracking), and IJobRepository.GetOrphanedConversionJobAsync.
All EF complex include chains relocated into repository methods; controllers now
call named query methods rather than composing raw IQueryable chains.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Six IUnitOfWork properties upgraded from generic IRepository<T> to domain-specific
typed interfaces (IJobRepository, IQuoteRepository, IInvoiceRepository,
ICustomerRepository, IBillRepository, IPurchaseOrderRepository). Each backed by a
concrete typed repository that encapsulates complex include chains previously
inlined in controllers.
Also adds IFinancialReportService and IOperationalReportService stub implementations
(NotImplementedException placeholders) to Application.Interfaces and Infrastructure.Services,
registered in Program.cs. These are the migration targets for ReportsController's
aggregate query methods in Phase 2.
No controller behaviour changed in this commit — all callers still compile because
typed interfaces extend IRepository<T>.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Email delivery failures and PDF generation errors now show a permanent
warning/error toast that requires manual dismissal, so users cannot
accidentally miss critical action-blocking feedback.
- ToastHelper: WarningPermanent TempData key + Warning/WarningPermanent
extension methods on both ITempDataDictionary and Controller
- SetNotificationResultToast: NotificationStatus.Failed now uses
ToastWarningPermanent (previously auto-dismissed in 5 s)
- InvoicesController.Send: TempData["Warning"] → TempData["WarningPermanent"]
when PDF generation or email dispatch fails
- InvoicesController.DownloadPdf: TempData["Error"] → TempData["ErrorPermanent"]
with the actual exception message so root cause is visible
- _Layout.cshtml: WarningPermanent hidden div
- toast-notifications.js: WarningPermanent handler (timeOut: 0)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PasskeyController: set LastLoginDate on passkey sign-in so Company Health
and audit pages show accurate last-login times (was always showing 'Never')
- Jobs/Index status modal: disable 'Notify customer' email toggle and show
warning when customer has notifications turned off; CustomerNotifyByEmail
added to JobListDto + JobProfile mapping + data-customer-notify attribute
- Quotes/Create: disable 'Send quote via email' checkbox with 'Notifications
off' badge when selected customer has email opt-out; ViewBag.CustomerEmailOptOutIds
added alongside existing CustomerTaxExemptIds pattern
- Quotes/Create: Quick Quote / Full Quote segmented toggle at top of form;
hides non-essential fields (dates, notes, tags, oven, discount, photos) in
Quick mode; selection persisted in localStorage
- InvoicesController Send action: improved error logging and user-facing
warning when PDF generation or email dispatch fails after status is saved
- item-wizard.js: guard item restoration with try/catch; ensure writeHiddenFields
always runs on form submit via capture-phase listener
- Help docs and AI knowledge base updated for all new features
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add "Don't ask me again" to passkey enrollment prompt (PasskeyPromptDismissed
field on ApplicationUser; DismissPrompt POST action; migration applied)
- Add Subscription & Features button to Companies/Index btn-group and
Companies/Edit header for direct navigation to SubscriptionManagement/Manage
- Add Edit Company back-link on SubscriptionManagement/Manage
- Remove duplicate AI Features section from Companies/Edit (managed exclusively
via Subscription & Features page)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Company-level toggle now grants access regardless of plan tier, checked
before the plan gate. Useful for enabling the feature on individual
Pro/Basic companies without upgrading their plan.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- HelpKnowledgeBase: full AI price check section (verdicts, confidence,
category paths, run limit, how to use, common questions)
- Inventory help article: new 'AI Catalog Price Check' section with
verdicts, step-by-step instructions, caveats, and on-page nav link
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace with generic 'AI', 'AI agent', or 'AI system' throughout.
Keeps the underlying vendor implementation details off the UI.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Button: 'Run/Re-run Price Check' -> 'Analyze Catalog with AI'
- Add info card explaining what the analysis does, verdict meanings,
and the disclaimer to verify operating costs before running
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GET: sets ViewBag.NextRunAvailable if last run was within 90 days;
view disables the button and shows the next eligible date
- POST: returns early with a warning if called before the 90-day window
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Haiku has generous rate limits so parallelism is safe again. Retry
logic catches any 429s. Progress estimate updated to ~8s per wave.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Was still estimating based on 3 concurrent waves (old model).
Sequential mode runs ~18s per batch, so 500 items ≈ 6 minutes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Skip $0-priced items (placeholders/category headers) in RunAiPriceCheck
- Build full category path (e.g. "Cerakote > Firearms") via BuildCategoryPath
so Claude receives coating-type context — Cerakote pricing differs significantly
from standard powder coat
- Update AI system prompt to instruct Claude to use the category path when
determining process type, equipment, cure times, and market rates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
500-item catalog was making 50 sequential API calls, causing progressive rate-limit
throttling (explains "super slow towards the end") and ~$3 in credits.
- BatchSize: 10 → 25 (word limits are in place; 25 items × ~80 tokens ≈ 2000
output tokens, well within MaxTokens=8192 — the original truncation cause)
- Run up to 3 batches concurrently via SemaphoreSlim(3) — independent API calls
with no shared state, so no growing context issue
- For a 500-item catalog: 50 sequential calls → 20 calls in ~7 parallel waves,
roughly 4× faster and 60% cheaper
- Dropped unused `costs` param from AnalyzeBatchAsync (system prompt has all costs)
- JS progress timing updated to reflect parallel waves
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Still seeing stubs despite MaxTokens=8192 — smaller batches and explicit
word limits in the prompt eliminate any remaining truncation risk.
- BatchSize: 15 → 10 (~1200 output tokens per batch vs. potential 3000+)
- Prompt: added 20-word cap on assumptions, 25-word cap on reasoning
- Prompt: strengthened "nothing before or after the '['" instruction
- Error log: now includes item IDs and first 300 chars of raw response
so the next failure tells us exactly what Claude returned
- JS timing: updated batch divisor from 25 → 10 to match actual batch size
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: MaxTokens=4096 was too low — 25 items at ~250 tokens each hit the
limit mid-array (logged error showed Path: $[17]).
- MaxTokens: 4096 → 8192
- BatchSize: 25 → 15 items (keeps each response well under the limit)
- StripJsonFences → ExtractJsonArray: now also handles prose before/after the
JSON array, and recovers truncated responses by finding the last complete
object and closing the array — so partial batches return whatever Claude
finished rather than nothing
- GET action: added try-catch around ResultsJson deserialization so a bad DB
row shows a friendly "re-run" warning instead of a raw error page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
File was written to repo-root wwwroot/ instead of
src/PowderCoating.Web/wwwroot/ — causing a 404 and MIME type refusal.
Moved to the correct location.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows a modal overlay with animated progress bar and batch-aware status messages
while Claude is analyzing. Progress animates in two phases: ease-out to ~85%
over the estimated duration, then a slow crawl to 99% so it never falsely
"completes" before the server responds.
- Overlay driven by CSS (hidden until .active added by JS)
- Item count passed from controller as data-item-count on the run button
- Batch count derived from item count (batches of 25) to show accurate
"Analyzing batch N of M…" messages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The auth panel CSS (brand panel gradient, form panel flex centering, feature list,
subtext color) was only defined in Login.cshtml's @section Styles — not in the
shared auth layout. EnrollPrompt used the same class names but had no styles behind
them, so the two-column layout collapsed. Added matching styles in EnrollPrompt's
own @section Styles block.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude reviews every active catalog item against the shop's own operating costs
and returns a per-item verdict (below-cost / thin-margin / high / ok) with a
suggested price range, cost floor, and assumptions.
- New entity: CatalogPriceCheckReport (JSON blob, archived per company)
- New service: IAiCatalogPriceCheckService / AiCatalogPriceCheckService
batches items 25 at a time to stay within model context limits
- Two new controller actions: GET AiPriceCheck (view report) + POST RunAiPriceCheck
- AiPriceCheck view: summary cards (counts by verdict), color-coded item cards
with Edit Price link, assumptions detail, and loading spinner on submit
- AI Price Check button added to catalog Index header
- Migration AddCatalogPriceCheckReport applied
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After password login, users are routed through /Passkey/EnrollPrompt
before reaching the dashboard. The page shows an Enable / Maybe later
choice using the auth layout for a clean full-screen experience.
Users who already have a passkey are skipped past instantly.
Removes the floating bottom-right card from _Layout — the dedicated
page is a better UX touchpoint (one moment, right after login, rather
than a floating card on every page).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch passkeySupported() from isConditionalMediationAvailable() to
isUserVerifyingPlatformAuthenticatorAvailable(). The conditional API
signals to iOS 17/18 that the page wants autofill passkey interception,
causing Safari to show its own native enrollment bottom sheet when the
password Sign In button is clicked. The platform authenticator check
simply asks if the device has biometrics, with no UI side-effects.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Derive ServerDomain and Origin from the incoming HTTP request instead of
appsettings.json, so WebAuthn works on localhost, dev, and production
without any environment-specific configuration. Removed IFido2 from DI
and the Fido2 appsettings block — PasskeyController instantiates Fido2
per-request via BuildFido2().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- HelpKnowledgeBase: passkey entry under USER PROFILE section with
full how-it-works detail (setup, login flow, browser requirements,
account-lock enforcement, per-device management)
- UserProfile help article: new Passkeys & Biometrics section between
Two-Factor Auth and Appearance, with setup steps, login steps,
browser compatibility note, and lost-device warning
- TOC nav link added to UserProfile article sidebar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shop floor workers can log in once with a password, enroll a passkey,
and use Face ID / Windows Hello / fingerprint for all future logins.
- UserPasskey entity + AddUserPasskeys migration (Fido2 v4.0.1)
- PasskeyController: RegisterOptions, Register, LoginOptions, Login,
Manage, Remove endpoints
- Login page: platform-aware button (Face ID / Windows Hello / etc.)
hidden automatically if browser doesn't support WebAuthn
- Post-login floating prompt to enroll on first use; session-dismissed
- Passkeys & Biometrics link in user dropdown menu
- Manage page: list registered devices, add new, remove individual
- passkey.js: targeted base64url conversion (only challenge + user.id
+ credential IDs) — fixes "Required parameters missing" error caused
by blindly converting rp.id and other string fields to ArrayBuffers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- StatusBump (GET + POST) now requires authentication; routes by job ID
instead of anonymous ShopAccessCode GUID; records actual user name in
status history instead of anonymous token string
- WorkOrder action generates a second "View Job" QR in the header linking
to the authenticated Details page (for verifying specs and seeing catalog
images on mobile); status bump QR updated to ID-based URL
- WorkOrder view: top QR added to header alongside job number; status bump
label updated (removed "no login required" copy)
- StatusBump view: updated form routing from asp-route-token to asp-route-id
- HelpKnowledgeBase and Jobs help article updated with two-tier QR docs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each catalog item now supports one optional image (jpg/jpeg/png/gif/webp,
max 10 MB). Uploading generates a 200x200 JPEG thumbnail automatically via
SixLabors.ImageSharp. Images are stored in Azure Blob Storage under a new
catalogimages container, keyed by {companyId}/catalog/{itemId}/.
- CatalogItem entity: ImagePath + ThumbnailPath (nullable string fields)
- Migration: AddCatalogItemImages applied
- ICatalogImageService / CatalogImageService: upload, thumbnail generation,
delete; old blobs replaced atomically on re-upload
- CatalogItemsController: Create/Edit accept optional IFormFile image;
Image(id, thumbnail) action serves blobs with [Authorize] so wizard users
can load thumbnails without CanManageProducts policy
- Catalog index (_CategoryNode): 40x40 thumbnail (or placeholder icon)
left of each item name
- Details view: image card in right column with click-to-full-size link
- Create/Edit views: file picker with live preview; Edit shows current
thumbnail with Remove checkbox
- Wizard (item-wizard.js): thumbnails in product list with hover preview
that follows the cursor (showCatalogPreview / moveCatalogPreview);
fixed Bootstrap d-flex !important bug that broke the filter box by
moving flex layout to an inner wrapper div
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GenerateAiProfileDraft endpoint builds suggested AI Profile text from
existing company config (ovens, workers, inventory categories, rates)
- "Generate from my settings" button wired in Company Settings AI Profile tab
- Add "hrs" unit label to Billable Hours/Month input in Company Settings and Setup Wizard Step 3
- Hide AI Quick Quote widget (commented out in _Layout) pending next release
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds MonthlyRent, MonthlyUtilities, and MonthlyBillableHours to CompanyOperatingCosts so fixed shop occupancy costs are recovered on every quote. The pricing engine converts these into a per-hour rate and applies it as a transparent "Facility Overhead" line between oven batch cost and shop supplies. UI added in Company Settings Operating Costs tab and Setup Wizard Step 3; migration AddFacilityOverheadFields applied. Help docs and AI knowledge base updated to cover the new fields and the revised quote pricing calculation order.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New AI Quick Quote floating button: staff type a verbal description to
get an instant price estimate for phone/walk-in customers; detected
color names are fuzzy-matched against inventory for stock status;
saves draft quote under a Walk-In / Phone customer with one click
- Inline customer change on Quote Details and Job Details: always-visible
native select with inline confirmation banner (no TomSelect dependency);
ChangeCustomer AJAX endpoints on QuotesController and JobsController
- Quote Edit page: customer dropdown is now editable (lock removed)
- Fix AutoMapper missing CatalogCategory -> UpdateCategoryDto mapping
that caused a crash on the catalog category Edit page
- Help docs and AI knowledge base updated for all three features
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>