The LOTULIS Journey
A day-by-day log of what was built, in plain language.
Note on tier-model history. Entries from March 2026 and earlier reference a "five-tier model" (Free / Starter / Pro / Business / Enterprise) that was in production at that time. The model was later restructured - current real tiers are
starter/pro/web_builder, withenterprisereserved as a per-contract custom marker (not a self-serve tier). Entries describing the older model are historical record, not current state. See docs/plans/white-label-master-plan.md § Tier model for the canonical current model.
This is the build log for LOTULIS. If you're a customer evaluating the platform, the public site is over there. If you're a curious technical reader, future hire, or future me - this is the receipts for how the product got made.
The bigger arc: LOTULIS started as photographer operations software, pivoted to a galleries platform any kind of professional photographer could use, and is on a path toward becoming a network where independent operators find and work with each other. This journal is the day-by-day evidence of that path being walked. Each month opens with a goal and closes with what landed and what slipped.
Day 1 - Tuesday, October 28, 2025
Morning
- Set up the project.
- Built the database.
- Got the first page loading.
Mid-day
- Built sign-up and log-in.
- Added two separate dashboards, one for business owners running a photography company, one for their customers.
Afternoon
- Built the photography jobs feature: business owners could create jobs, see them in a list, and edit them.
- Added Google login as a sign-in option.
- Added password reset.
- Added a setup wizard for first-time users.
Evening
- Built the client list.
- Added the ability to add new clients.
- Added the ability to browse past clients.
- Added basic numbers about each client.
Day 2 - Wednesday, October 29, 2025
Pre-dawn
- Overhauled the look of the entire admin area.
- Standardized the layout: sidebar on the left, search bar across the top, notification bell, user menu.
- Switched in a proper color palette and font.
Morning
- Built the photo upload system for property listings.
- Added drag-and-drop.
- Added an image gallery with full-size preview, download, and delete.
- Set up storage for videos, documents, and floor plans too.
Late morning
- Reorganized media management into accordion sections, one collapsible block each for Images, Videos, Documents, Floor Plans, and Interactive content.
- Added upload screens for videos, documents, and floor plans.
Day 3 - Thursday, October 30, 2025
- Added video link support so you could paste a YouTube or Vimeo URL instead of uploading the file.
- Built an interactive 3D content section.
- Added a count of how many photos, videos, documents, and floor plans each listing had.
- Tightened up spacing across the site for a more compact look.
Day 4 - Sunday, November 2, 2025
- Redesigned the public-facing property pages, the ones a customer sees.
- Cleaned up the "Connect with Me" section.
- Improved the agent profile photo layout.
- Added dynamic branding so each photographer's name and colors showed up.
- Built an image upload tool customers could use.
- Removed the agent section from the unbranded version of the page.
Day 5 - Thursday, January 8, 2026
Goal for the month: Take the platform from prototype to live in production by end of January. Real photography businesses able to sign up, set up their services, manage shoots and clients, and deliver media end-to-end.
The two-month gap from Nov 2 to Jan 8 wasn't silence. It was a parallel build sprint happening on a separate branch under an earlier working name - superadmin tooling, the full services and pricing system, the client portal, sign-up and onboarding flows, the deployment infrastructure, and the security rules that govern who can see what. That work is what gets pulled into the main project today, all at once.
- Imported a full superadmin area for managing photography businesses on the platform.
- Imported a complete services and pricing system: categories, modifiers, pricing rules, client segments, and price history.
- Imported a client portal where customers could view their listings and update their settings.
- Imported the supporting screens for sign-up, password reset, and account setup.
- Set up the deployment system so changes could be published automatically and checked before going live.
- Tightened up who could see and change what in the database.
- Added access rules to every table.
- Closed off security warnings flagged by automated checks.
Day 6 - Friday, January 9, 2026
A long evening session, fifteen separate pieces of work shipped between dinner and midnight.
Evening
- Built the calendar scheduling system from scratch: business owners could now book photography jobs into a real calendar with available time slots.
- Wired up the entire Services tab so the pricing screens actually saved to the database (before this they were UI-only).
- Built an analytics dashboard with charts.
- Added search and filtering across the admin pages, listings, jobs, clients.
- Added bulk operations: select multiple items at once and act on them together.
- Built the Admin Settings area.
- Polished the client portal so customers had more to do when they logged in.
- Added a comments system so customers could leave feedback on a listing.
- Built CSV export, download a spreadsheet of listings, jobs, or clients.
- Built CSV import, bulk-upload data from a spreadsheet.
Day 7 - Saturday, January 10, 2026
A late-night-into-morning marathon, then more work through the day. Theme: making things faster, more polished, and ready for real use.
Pre-dawn
- Added saved filters so business owners didn't have to rebuild the same searches over and over.
- Added pagination to all list views, no more loading 500 rows at once.
- Added lazy loading for photo galleries.
- Tuned the caching layer for faster page loads.
- Added performance indexes to the database.
- Wired up image delivery through a fast global content network.
- Set up a testing framework so changes could be checked automatically before shipping.
Morning
- Added a way to attach the same client to multiple businesses (some realtors work with several photographers).
- Added social login options.
- Added smoke tests, quick sanity checks that the site is alive after every deploy.
Afternoon
- Finished the pricing engine, supporting tiered prices, matrix prices, and stacking modifiers like a discount on top of a package.
- Polished the calendar once real bookings started flowing through it.
- Added "auto-create listing", when a job is created, the listing is created alongside it instead of as a separate step.
- Added delete protection so delivered listings couldn't be removed by accident.
Day 8 - Sunday, January 11, 2026
Design and money day.
Morning
- Pulled the full Design System v2 out of Figma and into the project, colors, spacing, typography, all standardized.
- Cleaned up the project's file organization.
Mid-day
- Applied the new design system across the site, branding refreshed, components updated.
- Polished the invoice flow.
Afternoon
- Built two-way sync between orders and calendar events, change one, the other updates.
- Added automatic draft invoice creation: when an order is marked complete, an invoice is created for it without anyone having to think about it.
- Polished the Services area.
Day 9 - Monday, January 12, 2026
A full-day security overhaul. The morning focus was locking down the deploy pipeline. The rest of the day was real features and polish.
Morning
- Added automated security scanning to the build pipeline, every change is checked against current security standards before it ships.
- Added security gates that block the deploy if something risky shows up.
- Added a local security check that runs before code is even pushed.
- Ran an industry-standard scan of the top ten most common security risks and fixed everything it flagged.
- Replaced weak random-number generation with strong cryptographic random.
Afternoon
- Added a developer contributing guide so future helpers know the rules.
- Reviewed the listings page end-to-end and rebuilt the rough parts.
- Added email delivery tracking, when an order email is sent, the system knows whether it landed.
- Added drag-to-reorder for bulk image rearranging.
- Added a PDF download button on order detail.
- Updated the calendar to show events outside the standard 9-to-6 window.
- Fixed five high-priority bugs in one batch.
Evening
- Added image preloading in the lightbox so flipping through photos felt instant.
- Upgraded the underlying runtime so the email system kept working.
- Cleaned up the project's tracker, closing items that were already complete but still listed.
Day 10 - Tuesday, January 13, 2026
Quieter day focused on documentation, integrations, and getting paid.
Morning
- Built the public help center structure, a place for customers and business owners to find answers without emailing support.
- Cleaned out duplicate folders that had been building up.
Evening
- Added cloud storage integrations so business owners could pull photos from their existing storage providers.
- Added Stripe payments, invoices could now be paid online.
- Polished a stack of small listing-page improvements.
- Removed a confidential business plan that had been accidentally committed to the project.
Day 11 - Wednesday, January 14, 2026
A security-first day that turned into a sprint on integrations and a marketing site.
Morning
- Ran a full security audit and produced a phased pre-launch checklist of everything to fix before going live.
- Fixed critical login and permission flaws the audit caught.
- Built a unified Google sign-in that covered three Google services at once: Google Drive, Google Calendar, and YouTube.
- Added a test suite with two layers, end-to-end browser tests and faster unit tests for the math-heavy parts.
Evening
- Built a centralized Data area for CSV imports and exports.
- Added two-way Google Calendar sync so business owners could see their photography schedule in their existing calendar app.
- Built the first version of the public marketing site, homepage, help center, pricing page, and API documentation.
- Cross-linked the marketing site and the app so visitors could move between them naturally.
Day 12 - Thursday, January 15, 2026
Marketing site polish and an under-the-hood signup fix.
Morning
- Pulled the standalone marketing site back into the main project so they shared one codebase.
- Added a script that runs all the project's checks at once.
Afternoon
- Built nine standalone feature landing pages with proper marketing navigation between them.
Late evening
- Built the foundation of the migration system, the tooling to bring data over from other photography platforms when a new business signs up.
- Added customer portfolio features.
- Added accordion sections to the client's listing detail page so long pages were easier to scan.
Day 13 - Friday, January 16, 2026
A long day of polish, mobile fixes, and a quiet payments-readiness milestone.
Morning
- Simplified the delivery workflow, removed a confusing in-between status that nobody used.
- Built a public download page for listings.
- Built admin email-template customization so business owners could write their own client emails instead of using the default ones.
- Added a date picker component that worked the same everywhere.
- Standardized page layouts across all admin and client pages so they felt like one product.
Afternoon
- Improved mobile scrolling on iPhone.
- Added the database columns needed for Stripe Connect, the payment-routing layer that lets each business owner accept payments into their own bank account.
- Added pre-push hooks that run code-quality checks before anything leaves a developer's machine.
- Set max-widths on every page for consistent reading width.
Evening
- Wrote end-to-end tests for the main customer workflows.
- Stabilized the test environment so the same tests would pass reliably on every run.
Day 14 - Saturday, January 17, 2026
A long workshop day. Lots of small redesigns adding up to a much more polished product.
Late evening (early hours)
- Added Square and PayPal as additional payment providers alongside Stripe.
- Restyled the portfolio page.
- Added a customer bio field so a profile felt more like a real person.
- Added a Dashboard link to the admin sidebar.
Afternoon
- Redesigned the client detail page from scratch.
- Added a delete button inside the upload modal, files could be removed mid-upload without leaving the screen.
- Unified all the different upload buttons across the site to use a single, consistent modal.
- Added a contact API for the public-facing pages.
- Polished property detail pages with more information.
Late evening
- Polished the mobile experience and tightened up public listing access.
- Redesigned the listings page with a card-based grid layout, much more visual.
- Improved the listing detail page and customer profile photos.
- Added in-app message notifications.
Day 15 - Sunday, January 18, 2026
The day MARCOTT became LOTULIS.
After three months of building under the working name "MARCOTT," the product got its real name. The new logo, brand colors, and product wordmark went in across the entire site in one sweep.
Morning
- Polished message notifications and the messaging system.
- Updated brand colors site-wide.
- Added test client data so the team could try things end-to-end without real customer data.
Mid-day
- Built a multi-admin client dashboard for clients who work with several photographers.
- Improved the pricing engine.
- Cleaned up automated security checks and removed credentials from test files.
Evening
- Rebranded the entire product from MARCOTT to LOTULIS. New logo, new wordmark, new "M" icon. Every page, every email, every dashboard.
- Built a calendar settings page where business owners could configure their working hours and scheduling rules.
- Subscription settings got a refresh.
- Renamed the internal "pricing rules" concept to "services", a clearer word for what they actually were.
- Polished the new-admin onboarding flow.
Day 16 - Monday, January 19, 2026
Morning
- Removed a batch of sensitive documents from the project history.
- Tightened the automated security scanner so it caught fewer false alarms.
- Refined how password-reset links route to the right page.
- Simplified the mobile menu on the public marketing pages.
Late morning
- Built a more secure two-step admin signup, a separate verification step before the new account becomes active.
- Added a real-time password strength meter on signup.
Afternoon
- Rebuilt the photo backbone for speed. Images now load in a fraction of the time and can be resized to fit any screen on demand, no extra copies, no waiting.
Day 17 - Tuesday, January 20, 2026
A big-launch day for an entirely new product feature: CopyPro.
Afternoon
- Finished rolling the new photo backbone out across every gallery and page.
- Added presigned upload URLs so very large files could be uploaded directly to storage without going through the server.
Evening
- Launched CopyPro. A new add-on for photographers who want their images watched for theft. It scans the public web for stolen copies of a photographer's work and reports matches.
- Built CopyPro's subscription tier (Free / Pro / Unlimited) with quota tracking.
- Built the admin dashboard for reviewing matches.
- Added a "scan" button right inside each image gallery.
- Added a queue system so big scans don't block the rest of the site.
- Added a blocklist for false positives.
- Added side-by-side image comparison so the admin can confirm a real match.
- Added zoom, bulk selection, and pagination on the matches view.
- Polished the subscription bar so it shows everything in one place.
Day 18 - Wednesday, January 21, 2026
A migration-system day with a few side quests in the email and superadmin areas.
Morning
- Switched CopyPro's background scanner from a basic cron to a proper job queue system that scales to many photographers.
- Built the migration import system foundation. This is the tooling that lets a new photography business import their existing data, past listings, past orders, past clients, from whatever system they used before.
- Fixed encryption and external-API compatibility for the migration system.
Evening
- Built a platform-level email inbox for the superadmin, replies to platform emails (sales@, support@) land in one shared inbox the team can read together.
- Refactored older code paths to use the new "services" naming.
- Added a compose-email button to the inbox.
- Added HTML sanitization on email display to prevent malicious content in incoming emails.
Day 19 - Thursday, January 22, 2026
The day AI photo editing showed up. Also a big day for the inbox.
Pre-dawn
- Polished email threading, replies now group with their original message correctly.
- Improved how the inbox handles oddly-formatted incoming emails.
- Grouped emails by conversation thread in the list view.
Afternoon
- Wrote a guide for what makes a page rank well on Google.
- Launched a blog section with the first post live.
- Built the AI photo editing infrastructure. Foundation for letting photographers send images to AI services for automatic enhancement, sky replacement, virtual staging, and similar.
- Built the AI editing library and shared types so multiple AI providers could plug in.
- Refreshed the rebrand across some lingering files.
Evening
- Wired up the migration system to download media files from the source platform.
- Wrapped the migration UI in the standard admin layout.
- Made migrations run in the background with real-time progress so they don't block the admin while they import for hours.
- Added impersonation support to migrations so the platform team can run a migration on behalf of a customer.
- Finished the AI photo editing integration.
Day 20 - Friday, January 23, 2026
A long, scattered day. AI editing went live, demo accounts were tried then pulled, and a brand-new broker branding system shipped at night.
Morning
- Merged the AI photo editing work into the main project.
- Built and shipped public demo accounts, anyone can try the platform with auto-cleaning sample data.
- Hardened the demo accounts against abuse.
- Reverted demo accounts entirely after a security re-think.
- Re-introduced demo accounts with stricter rules: payments and external integrations are blocked for demo users.
- Added a "Schedule Demo" modal on the marketing site.
- Polished the contact form.
Afternoon
- Wrote the first long-form feature documentation and a matching blog post.
- Added bulk download buttons across content sections.
- Moved AI provider keys from per-business to platform-level management, easier for the team to manage and rotate.
- Switched thumbnail generation to a faster on-demand pipeline.
- Added a customer-side download modal with image selection and cancellation.
- Added "Save to Dropbox" and "Save to Google Drive" buttons on the download page.
Evening
- Sped up uploads by running them in parallel.
- Built the broker branding system so a real estate brokerage can have its own colors, logo, and theme applied to all its listings and client pages.
Day 21 - Saturday, January 24, 2026
A focused evening on pricing depth.
Evening
- Added bundle discounts: stack a discount on top of a multi-service package automatically.
- Restored a price-history audit table so every price change is tracked.
- Made the price-history display readable for non-technical users.
- Made the diff view show only the fields that actually changed instead of the full record.
- Built a full segment-membership manager so business owners can group clients (top customers, agent partners, one-offs) and price differently per group.
- Added auto-assignment rules so clients land in the right segment automatically.
Day 22 - Sunday, January 25, 2026
An afternoon of order-form polish and a quiet but important deployment-safety upgrade.
Afternoon
- Polished the inline order editor, pricing, discounts, and data all stay in sync as fields change.
- Fixed how discounts and tax show up on orders.
- Refined price recalculation so it triggers when a service is set to free.
- Added the ability to put multiple clients on a single order and filter the service list while editing.
Evening
- Built a deploy-approval system so production releases require explicit confirmation before they go live, no accidental pushes.
- Added an animated progress display when an order is being confirmed so the admin sees it working.
Day 23 - Monday, January 26, 2026
A short day focused on hardening the math.
- Wrote a test suite for the pricing engine, every discount, modifier, and tax rule now has automated checks.
- Consolidated all pricing logic into one place so prices can never disagree between screens.
- Hardened penny-rounding so totals stay precise across edge cases.
Day 24 - Tuesday, January 27, 2026
A migration-system push plus the early planning for the editor collaboration system.
Morning
- Built a feature where importing a listing now auto-generates the public listing website for it, one less manual step.
- Added a search bar to the migration interface so admins can find specific listings in a large import.
- Added a download toggle to control whether media files come over with each listing.
Afternoon
- Tuned the migration job triggering so big imports run reliably end to end.
- Added a way to activate archived listings, bringing an old listing back to life when a property comes back on the market.
Evening
- Designed the architecture for a team and editor collaboration system, the foundation for letting business owners hand work off to in-house and outside photo editors with a portal of their own.
Day 25 - Wednesday, January 28, 2026
A milestone day. The editor portal launched, the client invitation flow shipped, and the legal pages required for app-store reviews went up.
Morning
- Polished an order button placement on the customer side and a few pricing edge cases.
- Built the team management area where business owners can add staff and set who can do what.
- Launched the Editor Collaboration System. Business owners can now create editing sessions, hand them off to an editor (in-house or contracted), and review the results in a dedicated editor portal.
- Built the editing-sessions tab on the listing detail page so it's clear what's been edited, what's in progress, and what needs review.
- Wrote the privacy policy and terms of service required by external verification (Google OAuth and similar).
- Polished the legal pages.
Afternoon
- Simplified the new-client page so it matches the modal flow used elsewhere, one consistent way to add a client.
- Built the client invitation flow. Business owners can email a client a sign-up invitation; the client lands directly in their portal without going through public signup.
- Tightened the deploy-approval gate from 1 to 3 approvals for added safety.
Evening
- Added auto-linking so when a client signs up with an email already in another business owner's account, both businesses see them as the same person.
- Closed off a search-bar input boundary, hardening it against malformed filters.
Day 26 - Thursday, January 29, 2026
The day before launch.
The migration system, the tool that would let new customers bring their old data over on day one, had been quietly misbehaving. Stuck imports would sit forever in a "processing" state with no way to recover. So the morning was spent debugging it, watching jobs run, adding logs, peeling back layers until the failure mode was clear. Then the fixes went in: a recovery button for stuck imports, real-time progress polling so the admin can see things actually moving, and a clean-up of all the noisy debug logs that had been added during the hunt.
The afternoon turned to look-and-feel. Three completely different brand color themes were drafted to explore what the product could become, a dark "Deep Space Copper," a warm "Monochrome Clay," and a fresh "Mint + Terracotta", paired with an interactive dashboard mockup so you could see each one in motion. None shipped to production today; this was a sketch session for a future direction.
Other work of the day
- Added the option to create a new client right inside the listing activation flow.
- Consolidated repeated security checks across the migration system into a single shared helper.
- Reorganized the new-order form so address comes first, then property details, then options, a cleaner top-to-bottom flow.
- Updated the logo's icon shape to a softer rounded square.
Day 27 - Friday, January 30, 2026 - **LAUNCH DAY**
LOTULIS went live.
After three months of building, the project flipped from "in development" to "in production." The day was less about new features and more about the final layer of polish, the kind of work you do when real customers are about to land on your site for the first time.
The launch-day push had three big parts:
A final security pass. Every entry point was reviewed and tightened. Login and password-reset pages were rewritten to never reveal whether an email exists, eliminating an entire class of attack. File-download links were locked down so a malicious file can't be served as a web page. Rate limits were added to the email-checking endpoints with random delays so attackers can't time their guesses. Stricter validation was added to listing data submissions.
A new security monitoring system. A backend that watches for suspicious activity, with AI-powered investigation that triages alerts, suggests actions, and can auto-execute the safe ones. The superadmin team got a dashboard for reviewing alerts and approving actions, so the platform watches itself, and humans only get involved when something needs judgment.
The broker branding library. Twenty of the major real estate brokerages, Compass, Coldwell Banker, Berkshire Hathaway, Keller Williams, RE/MAX, Sotheby's, Douglas Elliman, eXp Realty, Howard Hanna, Century 21, and ten others, were fully themed with their real brand colors and logos. Any agent from any of those brokerages can land on a properly-branded experience from day one.
Migration paid checkout went live too, new customers can pay for their data import directly through the site. The migration system also got a stale-session cleanup so abandoned imports don't pile up.
Then the documentation was officially updated. Every status header, every readme, every roadmap was marked LIVE IN PRODUCTION with the date: January 30, 2026.
That was launch.
Goal reached - The platform shipped to production on schedule. Sign-up flows, services and pricing, client management, calendar, deliveries, payments, and the full superadmin layer all went live. The platform's rebrand happened mid-month and held. Twenty broker brands themed end-to-end. The bones were real on day one.
Day 28 - Saturday, January 31, 2026
The first day after launch. A short list, but each item set up something larger that came later in the week.
Evening
- Built the full search-engine-optimization layer: every public page now has the right titles, descriptions, structured data, and crawler hints to rank well.
- Added a default social-sharing image, when a customer shares an LOTULIS link in a message app, it now shows a real preview card.
- Started Team System v2, the foundation for paying photographers and editors. The first piece: editable compensation per team member, with a full audit trail of every change.
- Polished the login flow and the way the sitemap and robots files are served.
Day 29 - Sunday, February 1, 2026
Goal for the month: Platform hosting its first photography business and testing the image upload, delivery, and download features on real clients.
The biggest day of the post-launch week. Twenty-five separate pieces of work shipped, a team system rebuild, a white-label tier of features, analytics for both admins and customers, and the foundation for photographer scheduling.
Pre-dawn
- Continued Team System v2: built assignments and performance tracking, admins can now assign team members to jobs and see how each person is performing.
Morning
- Added a code-review checklist that runs before every commit so quality stays consistent.
- Built phase 3 of the team system: photographer scheduling and expense tracking.
- Built phase 4: earnings tracking and payout batching, the system can now calculate what each team member is owed for a given period and group payouts together.
- Refined the "join team" link so it works cleanly for users who already have an account.
- Built the photographer self-service portal with proper routing, photographers now have their own login destination instead of being mixed into the admin flow.
- Walked the entire site front-to-back catching dead buttons, stale links, and a missing contact page during a polish audit.
- Added auto-save everywhere settings and team forms appear, no more "did I click save?".
- Separated photographers and editors into their own list views.
Mid-day
- Updated security rules so embedded videos and 3D property tours work properly inside the site.
- Added website analytics so the team can see how many people visit each page and where they come from.
- Built listing analytics for admins and customers, both can now see how many people viewed a listing, which photos got the most attention, and where viewers came from.
Afternoon
- Built the white-label branding system, paid-tier customers can replace LOTULIS branding with their own across listings and emails.
- Added calendar-event email notifications: shoot confirmations, reminders, reschedules, and cancellations all go out automatically.
- Branded the forgot-password and reset-password pages so the experience matches the rest of the white-labeled site.
Evening
- Improved the calendar integration: photographers can connect their personal Google calendar so shoots show up there automatically.
- Built a superadmin integrations hub where the team can manage external service connections in one place.
Day 30 - Monday, February 2, 2026
A single shipping item.
- Customers now get an automatic welcome email when they're imported via spreadsheet, no more silent additions.
Day 31 - Tuesday, February 3, 2026
The day the subscription tier system became real.
Until today, the codebase had the idea of plan tiers (Free, Starter, Pro, Premium, Enterprise), but features weren't actually locked behind them. Today changed that.
Afternoon
- Built the subscription tier lock system: every premium feature now checks the customer's plan before letting them use it. A locked feature shows an upgrade prompt instead of just disappearing.
- Put a quiet "shadow gate" on more than a hundred backend endpoints, they now log when a lower tier hits a higher-tier feature, without blocking yet, so the team can see how often it would happen before turning enforcement on.
- Replaced placeholder images on the marketing feature pages with real screenshots of the actual product.
- Reorganized the white-label system so it fits cleanly into a unified 5-tier model.
- Wrote up the tier-system planning documents and a pricing preview.
- Updated the public marketing pricing page to reflect the new tier system.
Evening
- Locked in a formal limits contract, every feature has a documented maximum per tier (number of listings, storage, team members, etc.) so the same rules can be enforced everywhere.
- Cleaned up the team assignment data model: removed redundant fields, made the "lead photographer" relationship single via a database constraint instead of a manual flag.
- Built the first three phases of the team-assignments feature: the database, the availability checker, and the UI for assigning a team member to a shoot.
- Added a retention-and-archive policy, old data ages into archive cleanly instead of cluttering active views.
Day 32 - Wednesday, February 4, 2026
A long workshop day. Team assignments wrapped up, support ticketing went live, and a security scanner came online.
Pre-dawn
- Finished phases 4 through 6 of team assignments: hooked it into the order form, added the database triggers that fire notifications when an assignment changes, and shipped the in-app notifications.
- Polished the team assignment UI to match the design system.
Morning
- Built a security scanning system, automated checks that confirm code and dependencies are current with security standards.
- Built a support ticketing system, customers can now open tickets directly from inside the platform instead of emailing.
- Added notes to calendar events.
- Polished the superadmin signout flow.
- Added William Pitt Sotheby's to the broker theming library.
Evening
- Wrote a phase 8 rollout checklist for the next stretch of work.
- Made downloads free for everyone instead of being a paid feature, the right call for a delivery platform.
- Hardened Google sign-in so it can't be used to create an account that doesn't already exist (only existing users can use it to log in).
- Improved the security scanner to catch a broader class of bugs.
- Corrected the tier pricing model: Free tier listings really are free with no per-listing limit, and the limits live on the paid tiers instead.
Day 33 - Thursday, February 5, 2026
The day the Free tier became real to a customer.
Yesterday's tier system was the plumbing. Today was the fitting: every Free-tier user now sees clear locks, upgrade prompts, and quiet limits that match what was promised on the pricing page.
Morning
- Locked Jobs, Calendar, Services, and Invoices for Free-tier users with friendly upgrade prompts instead of empty screens.
- Refreshed the pricing-info banner.
- Added a services feature on the Starter tier that had been omitted.
Mid-day
- Added a tier badge to the admin header so business owners always know which plan they're on.
- Wrote the tier-audit protocol, the formal checklist for verifying a tier is enforced everywhere it should be, with a section on preventing common workarounds.
- Locked Free-tier customers out of delivery emails, multi-client creation, and the website-builder feature.
- Locked the Messages section for Free-tier customers.
- Turned on backend enforcement for all Free-tier blocked features (the "shadow gate" from yesterday now actually blocks).
Evening
- Built an interactive testing checklist and a "feature keys" reference modal for confirming a tier's behavior end-to-end.
- Started Starter-tier enforcement.
- Added an apartment/unit field to the listing form.
- Polished admin subscription details and an email-search edge case.
- Synced the plan and plan tier columns whenever someone changes tier so they never drift.
Day 34 - Friday, February 6, 2026
A performance and cleanup day, with one big win and a stack of small ones.
Morning
- Added a "View download page" link directly on each listing card.
- Major cleanup pass across docs, tests, and broker brand assets, removed a stack of files that had been building up since the early days.
Afternoon
- Made the listings page roughly twenty times faster. It was running 120 separate database lookups every time it loaded; now it does the same job in 6. Customers with hundreds of listings will feel this every day.
- Polished the empty state on the listings page and hid public website URLs for listings that have their site disabled.
Evening
- Tuned the photo cache for faster repeat loads and stripped hidden camera data from generated thumbnails (smaller files, no leaked location info).
- Locked YouTube embeds, advanced video display options, and editor collaboration sessions behind the paid tiers, these were always premium-intended and now they actually are.
- Hid premium-only navigation items for Free-tier users and improved the superadmin's "view as customer" mode.
- Added superadmin ticket creation with no attachment restrictions, the support team can attach anything they need to a ticket.
Day 35 - Monday, February 9, 2026
Two days off, then a creative-and-storage focused day.
Afternoon
- Built a hero-image slideshow for the public listing pages, every property now opens with a rotating set of hero photos instead of a single still.
Evening
- Polished the slideshow and made the "save to cloud" buttons feel snappier.
- Built a long-term archive layer for original photo files. When a customer uploads a 50-megabyte original, the optimized version stays on the fast layer, and the untouched original is moved to a cheaper, slower archive, fetched only when someone explicitly asks for the original. Big storage cost reduction without losing any pixels.
- Added the multi-part upload support that big editing-session files need.
- Added the cleanup routines that delete archived files when an editing session is closed out.
- Built the frontend tooling for sending files directly to the archive layer from the browser.
Day 36 - Tuesday, February 10, 2026
A short evening of high-value editor-side polish.
Evening
- Added a "resend invitation" and "send password reset" button on the client-management page so business owners can re-invite a client without leaving the screen.
- Built a Dropbox-style image review interface for editing sessions, editors can browse hundreds of edited frames quickly, approve or reject in batches, and compare the before/after on each one. Familiar territory for any editor who's done a cloud handoff before.
- Wired the editor session up to the new long-term archive storage layer so editors can download original files when they need them.
Day 37 - Wednesday, February 11, 2026
A full hardening day for the new storage system. Less new functionality, more "make sure the new layer can't be abused."
Morning
- Polished the raw-files display, smoothed out the publish flow, and tightened a few UI edges from yesterday's editor work.
Afternoon
- Locked down the new archive storage layer against a list of risks the team had identified, proper signed URLs, scoped access, no leakage of internal paths.
- Wrote up findings from the migration system code review and addressed each one.
Evening
- Completed a security audit pass: third-party dependencies bumped to current versions, generic error messages so internals stay private, stricter validation on uploaded files.
- Capped how many files can be downloaded in parallel to three at a time, with automatic retry and timeout, large sessions no longer overwhelm the network or fail halfway through.
Day 38 - Thursday, February 12, 2026
Download performance day, with one customer-facing bonus at the end.
Pre-dawn
- Built server-side ZIP streaming for editor session downloads, instead of waiting for the whole archive to package up, the file starts downloading immediately and streams as the server zips it. No more spinner-and-wait on big sessions.
- Added the remaining utilities the editor sessions needed to be fully functional on the new storage layer.
- Applied a stack of work-in-progress polish: upload UI, thumbnails, analytics, and the background processing layer.
Morning
- Closed out two batches of critical and high-severity findings from the latest security audit.
- Consolidated duplicate file-handling types into one shared definition and surfaced error displays where they had been silent.
- Improved download performance further and tuned listing freshness so new listings show current data immediately.
Evening
- Built shareable video links with proper preview cards. When a customer pastes a property video link into a message, social post, or email, it now shows a real preview with the property image, title, and description, same treatment that big-platform links get.
Day 39 - Friday, February 13, 2026
A design polish day with a side of editor improvements.
Morning
- Closed off a stack of critical and high-severity calendar bugs that had been sitting in the tracker.
- Turned on notifications for team members so editors get notified when a session is assigned to them.
- Walked the entire site with a design audit, found and fixed places where the brand color had been hardcoded instead of pulling from the design system, plus a handful of token mismatches.
- Cleaned up a double-border on focused input fields that was making text fields look uneven.
Afternoon
- Improved the editing session experience: better draft notifications, more API hooks for the editor portal, and a download progress bar so editors can see big file transfers actually moving.
Evening
- Moved session publishing to a background job, when an editor finalizes a session, the heavy work happens out of sight instead of making them wait at a spinner.
Day 40 - Saturday, February 14, 2026
A performance day with one big customer-facing feature.
Morning
- Tuned listings page database queries to fetch only the columns each view actually needs, faster loads, lighter payloads.
Afternoon
- Optimized the way listing thumbnails are generated and queued the smaller derivatives to start producing themselves the moment a session is published, so they're ready before anyone clicks.
- Built a unified loading state across the admin area: every page now shows a proper skeleton while it loads instead of flashing empty cards or a half-rendered layout.
- Centralized the logic that decides which email address replies to outgoing emails should land on, with proper fallbacks and validation so a customer reply never disappears.
Evening
- Added recursive folder search to the Dropbox integration. Business owners importing photos from Dropbox can now search through nested folders instead of only browsing one level at a time.
- Hardened the cloud import pipeline (Dropbox, Google Drive) and rerouted message attachments through the new photo backbone for faster delivery.
Day 41 - Sunday, February 15, 2026
A polish marathon, plus two real features.
Morning
- Fixed how customer-portal pages compute photo URLs so old listings render correctly.
- Closed an import edge case where some files were being counted twice.
- Polished a few rough edges in the customer UI.
Afternoon
- Added profile image upload to admin settings, business owners can now set their headshot directly in the platform.
- Tightened the routing for listing image uploads so they always land in the correct storage bucket.
- Closed a string of related photo-display bugs: missing derivatives, stale file paths, mixed-storage edge cases, all the inevitable wrinkles after the photo storage layer was rebuilt last week.
- Removed CSV import logic that was looking for first/last name columns that no longer existed.
Evening
- Built a complete admin email notifications system. Business owners now get email pings for the events that actually need their attention, new orders, delivery confirmations, key client actions, without spamming them for actions they took themselves.
- Updated the reply-to lookup for outgoing emails to use the current column name.
- Sped up the customer dashboard by collapsing per-listing database queries into a single query.
Day 42 - Monday, February 16, 2026
A polish-and-plumbing day. The standout was making the admin shell feel instant.
Morning
- Added a Support link to the calendar menu and tightened up a chart's dimensions.
- Cached the user record at the layout level so the same data isn't fetched repeatedly across pages.
- Built a persistent layout shell. Navigating between admin pages no longer triggers a flash of empty layout, the sidebar and header stay mounted and only the page content swaps in.
Mid-day
- Fixed delivery emails so they resolve the property address correctly even when the listing record is missing one (falls back to the order record).
- Made listing website slugs regenerate when the property address is edited.
Afternoon
- Polished drag-and-drop reordering in the photo galleries for Edge browser users and floor plan reordering.
- Wired client-side gallery reordering and visibility toggles through proper server actions instead of direct database calls.
Day 43 - Tuesday, February 17, 2026
Client-side polish day, with one strategic rewrite document drafted in the background.
Morning
- Enabled client-side editing on a few client-portal screens that had been locked out, with proper authorization checks.
- Drafted three planning documents: a galleries-rewrite plan, a new platform charging model, and a tier-system restructure.
- Unified the accordion component used across admin and client views so they look and behave the same.
Afternoon
- Polished the broker theming pipeline so dark-on-light logo variants work properly and fall back gracefully when the asset isn't available.
- Cleaned up the public order form: removed a stale email-verification flow, polished the pricing display, scrubbed internal status from the public payload.
Evening
- Built file uploads into the client portal. Customers can now upload their own files into a job (think: their own headshots, branding assets, deed scans) with a per-account storage cap and server-side validation to prevent abuse.
- Improved text contrast on the jobs list for readability.
Day 44 - Wednesday, February 18, 2026
A massive cleanup day across the pricing engine, with a unified download system as the headline feature.
Evening
- Built a unified download system. Every download, single file, batch, full session, ZIP, public, private, now flows through one consistent pipeline with proper authorization. No more inconsistent behavior between admin downloads and customer downloads.
- Removed a stack of orphaned API endpoints that didn't have proper authorization or business-tenant scoping.
Late evening
- Added two new pricing strategies: markup pricing (charge a percentage on top of cost) and lookup pricing (price varies by a value the system looks up, square footage, room count, etc.).
- Implemented fixed-amount discounts for client segments.
- Closed a stack of bugs in the pricing engine and segments system: expiration dates not being passed through the bulk-add modal, deleted segments leaving stale references on services, decimal matrix prices being rejected, category modifiers not being executed, and a half-dozen others.
- Added a guard so deleting a service category cleans up references on every dependent service.
Day 45 - Thursday, February 19, 2026
A planning day. The kind of day that doesn't ship features but reshapes the next several months.
The morning was spent rewriting how the product is framed to the world. For the first three months the platform was sold as a "real estate listings" tool. Today the team began the shift to a broader "galleries" framing, the product is the same, but it can serve more than just real estate (portrait photographers, event photographers, commercial work). The marketing site, the URL structure, and the database vocabulary all need to follow.
The afternoon was a deep architectural research session on a problem that had been quietly causing friction: what happens when one person is both an admin (running a photography business) AND a client (a customer of another business)? The answer the team landed on is "asset-centric", a person isn't a single role, they're a set of relationships to specific assets (this listing, that order, this gallery). The role they see depends on what they're looking at. This unlocks all the dual-role and multi-business scenarios that had been awkward to model.
In parallel, a new platform pricing model was sketched out, moving from feature-tier limits to storage-based pricing, which fits the gallery framing better.
Other work that shipped today
- Tightened cache headers on admin asset uploads so they cache aggressively in the browser.
- Added staged loading states to the download modals so the customer sees real progress instead of a single spinner.
- Closed a security finding in the segment membership routes by routing them through the standard admin-identity helper.
Day 46 - Friday, February 20, 2026
A long systems-cleanup day. Two sweeps through the services audit and the pricing engine audit cleared a lot of accumulated debt.
Morning
- Removed legacy backward-compatibility aliases in the services system and added a uniqueness constraint on category codes so duplicates can't sneak in.
- Stopped persisting Google sign-in profile photo URLs because they expire, the system now stores its own copy instead.
- Tuned Dropbox and Google Drive imports so videos and documents arrive in the correct order.
Afternoon
- Drafted the implementation tasks for a new payment system overhaul.
- Removed property addresses from public video page metadata for privacy.
Evening
- Closed nineteen separate bugs across the services and pricing audits in three batches, type mismatches, dead scaffolding code, modifier-type schema misalignments, and a stack of edge cases that had been sitting in the tracker.
Day 47 - Saturday, February 21, 2026
A short focused day. The marketing rewrite from "listings" to "galleries", planned two days earlier, started landing in actual code.
- Closed another batch of pricing engine audit items.
- Began the marketing copy rewrite. Public-facing language across the site started shifting from "real estate listings" to "galleries", broader, more inclusive of every kind of professional photographer.
- Removed a stack of dead code from the pricing engine (unused fields, scaffolding never wired up).
Day 48 - Sunday, February 22, 2026
Design system enforcement day, plus the marketing rewrite finished landing.
Afternoon
- Finished the listing-to-gallery rename across all marketing pages and rewrote the pricing page.
- Migrated roughly a thousand raw color values across the codebase to semantic design tokens. Before today, brand colors were hardcoded in many places (so changing a color meant a global find-and-replace). Now every color reference points at the design system, and a single change updates everywhere it appears.
- Turned on a code-quality rule that fails the build if anyone adds a raw color in the future.
Evening
- Cleared a final wave of pricing engine audit items: custom matrix dimensions, ownership guards, taxable-modifier behavior, and modifier-level tax flags.
Day 49 - Monday, February 23, 2026
A platform upgrade day, plus invoice plumbing.
Afternoon
- Removed duplicate database queries on public pages and scrubbed personal information out of server logs.
- Reworked public downloads to package files in the customer's browser instead of on the server, much faster, lighter on infrastructure.
- Closed off non-critical security warnings flagged by an automated dependency scan.
Evening
- Performed a major framework upgrade, bumped the underlying tech stack to its latest stable release, including a one-time codemod across nineteen files to update an API change. All tests pass on the new version.
- Removed an unused legacy table from the database.
- Fixed invoice totals by anchoring them in the database with a trigger so they can't drift between the line items and the displayed total.
- Added a dedicated endpoint for marking invoices paid or void.
- Wired automatic invoice emails into every path that creates an invoice, no more silent invoice creation.
Day 50 - Tuesday, February 24, 2026
A marathon day. Two major audits, the invoice system and the calendar system, were systematically worked through. Forty-plus pieces of work shipped between morning and midnight. The most consequential customer-facing additions were partial payments, payment receipts, and an admin view for tracking every transaction.
Morning
- Built a public invoice page. Customers can now click "View invoice" in their email and see their invoice without logging in, with a payment button right there.
- Hardened how Stripe webhooks process payment events using a two-phase pattern that prevents accidental double-processing.
- Corrected the tax-rate display on customer invoices.
- Made all Stripe webhook handlers fail safely when database errors occur, failures now block the webhook from acknowledging instead of pretending success.
- Added audit logging for every webhook event so payment issues can be traced after the fact.
Afternoon
- Added in-app notifications for invoice events: invoice sent, marked paid, etc.
- Added payment confirmation emails for every payment path, Stripe, manual, partial, you name it.
- Built an invoice edit page for draft invoices so business owners can adjust line items before sending.
- Built an automatic overdue-invoice detector that runs in the background and flags invoices past due.
- Made order modifiers (discounts, add-ons) flow into invoices as signed line items.
- Aligned the job edit page side effects with the bulk operations endpoint so updates trigger the same downstream actions either way.
- Surfaced invoice email failures to the admin so any delivery hiccup is visible.
Evening, calendar audit
- Fixed calendar event queries to use proper time-overlap logic (was missing edge-case events).
- Composed photographer sync location from the property address fields so Google Calendar shows where to go.
- Aligned calendar event type and status filters with the actual database values.
- Added a duplicate invoice guard.
- Converted invoice emails to a proper template system so they look consistent.
- Added a loading skeleton on the invoice detail page to prevent layout bounce.
- Surfaced calendar API errors as toast notifications instead of silent failures.
- Closed a stack of calendar security and data-integrity issues.
Late evening
- Built the admin payment transactions view, a complete history of every payment attempt, success, refund, and dispute, scoped to the business.
- Added partial payment support for invoices.
- Wired the admin's timezone into calendar availability and Google Calendar sync so events show up at the right local time.
- Added rate limiting to the checkout endpoint.
- Added an automatic cleanup for abandoned checkouts that ages stale ones to "expired" status.
- Added a foreign-key constraint linking calendar events to team members so orphaned events can't happen.
Day 51 - Wednesday, February 25, 2026
The day after marathon day, with another fifteen pieces of work, most of them invoice and calendar follow-throughs.
Morning
- Built payment receipt PDFs. Customers can download a proper PDF receipt for any payment they've made.
- Blocked common bot-probe paths (
/wp-admin,/wp-login, etc.) at the front door with a fast 404.
Afternoon
- Consolidated payments into the invoices page with summary cards and a date filter, the team can see the entire money picture from one screen.
- Closed another batch of calendar audit items.
- Built public invoice checkout, customers can pay without logging in. Just click the link in the email, see the invoice, pay. No account required.
- Fixed calendar notifications so emails and in-app pings fire for every event action (create, reschedule, cancel, confirm).
Evening
- Added invoice email tracking and automatic reminders. The system knows when an invoice email lands, when it's opened, when it stays unpaid, and sends reminders on the right schedule.
- Made the calendar use the admin's own scheduling settings (slot size, working hours) instead of hardcoded defaults.
- Added a video thumbnail poster in gallery cards so videos don't show as black squares before playback.
- Added per-service duration so booking availability is accurate, different shoot types take different amounts of time, and the calendar now respects that.
- Added a "Revenue Collected" metric to the admin dashboard.
Day 52 - Thursday, February 26, 2026
A short cleanup day for the invoice + services systems.
- Added a flag on services that prevents two incompatible services from being combined into a single order, useful for shoot types that physically can't happen together.
- Made invoice emails always pull the live business name instead of a stale cached field.
- Cleaned up the invoice send code so it lives in one shared helper instead of being duplicated.
- Expanded the file types accepted on uploads (more image formats, more document formats).
Day 53 - Friday, February 27, 2026
End-of-month cleanup, plus a major platform-stack upgrade as the closing act.
Afternoon
- Added email and phone validation on the public order form so customers can't accidentally submit malformed contact info.
Evening
- Improved Google Calendar sync coverage for background events.
- Performed the largest platform-stack upgrade since launch. The web framework, the styling system, and the code-quality tooling all jumped a major version. All tests pass on the new stack, no customer-facing changes, but the foundation is now on the latest stable releases.
Goal reached: The founding photography business is live on the platform and the image upload, delivery, and download flows were tested with real clients.
Day 54 - Sunday, March 1, 2026
Goal for the month: Finalize admin tiers and fully review invoicing and orders so the ordering, scheduling, and invoicing systems can start being used with real-business clients.
A March kickoff day focused on calendar polish, database performance, and the design for travel-time-aware scheduling.
Morning
- Moved the photographer Google Calendar sync into a background job with automatic retry, slow Google API responses no longer block the main app.
- UX polish across multiple admin views and proper sanitization on phone inputs.
- Tuned the database access rules for performance. A single change wrapped the user-identity check so query plans can cache it instead of recomputing per row, measurable speedup on the busiest tables.
- Closed all remaining items from the calendar audit.
Afternoon
- Hid cancelled events from the calendar view by default (still visible with a toggle).
- Refined notification handling so confirming a rescheduled event sends a single notification.
- Hardened the audit-log identity check so it always derives from the verified server session, never from client-supplied data.
- Cleaned up duplicate database indexes that had built up over time.
Evening
- Drafted the design for travel-time-aware calendar scheduling, the calendar will eventually understand drive time between consecutive shoots and refuse to book back-to-backs that don't physically fit.
- Cleaned up a hydration mismatch on the marketing pages.
- Threaded business-hours awareness through every reschedule and event-card view so the calendar respects the admin's working hours everywhere it appears.
Day 55 - Monday, March 2, 2026
The storage foundation went in, the groundwork for the new tier system that's the focus of the month.
Morning
- Removed CSV import/export buttons from the listings and orders pages (those flows are moving to a centralized data area instead).
Afternoon
- Built the storage tracking foundation. The platform now knows exactly how many bytes each business is using across photos, videos, documents, and editor session files, broken out by category, and updates in real time as files are uploaded and deleted.
Evening
- Added storage enforcement: uploads that would push a business over its limit are blocked with a clear message.
- Built a background reconciliation job that re-counts storage from scratch periodically to catch any drift between the live counter and reality.
Day 56 - Tuesday, March 3, 2026
A long day. The full new tier system rolled into place, feature flags, a redesigned subscription page, storage add-ons, warning emails, and the foundation for migrating every existing business onto it.
Morning
- Hardened the storage enforcement and tightened the counter so canceled uploads are properly subtracted.
- Backfilled the file-size column on existing records so every file's size is known.
- Wired the delete path through the storage counter so removing a file actually frees the quota.
Afternoon
- Added a new "Base" tier to the subscription system, the entry-level paid plan.
- Built the new tier system as a switch. A platform-wide flag turns the new system on or off per business, with overrides for testing, so the team can validate it on a few real customers before flipping it on globally.
- Added storage visibility everywhere it matters: in the admin dashboard, in the upload screens, in the settings area.
Evening
- Redesigned the subscription page. New layout, clearer pricing, side-by-side plan comparison.
- Built dual checkout, customers can buy a subscription and a storage add-on in the same payment flow.
- Added storage warning emails. When a business hits 80%, 90%, and 100% of its storage limit, they get an email, once per threshold per period, no spam.
- Replaced a hardcoded download file count limit with server-stored download manifests so big batches work properly.
- Tuned the cloud-import flow so importing thousands of photos stays within memory limits.
Day 57 - Wednesday, March 4, 2026
The day the tier migration actually ran in production.
Morning
- Built and ran the production migration that moved every existing business onto the new tier system. A state-machine-driven script with staged rollout and full verification, every business landed on the right plan with the right entitlements, and the team has a complete audit trail of what changed for whom.
Afternoon
- Added support for multi-client users. When the same person is a client of more than one business, common with realtors who hire several photographers, the platform now recognizes them as one identity across businesses instead of forcing duplicate accounts.
- Built a duplicate-client-record merge tool so historical duplicates can be consolidated.
Day 58 - Thursday, March 5, 2026
The day the old tier system started being dismantled.
Yesterday the new tier system was running in production. Today the old one began to come out, every feature lock, every entitlement check, every "you need to upgrade" prompt that belonged to the old plan structure was either deactivated or queued for deletion.
Evening
- Turned the old feature-check code into a no-op so it stops blocking anything (deletion will come later, this step keeps the call sites intact in case any need to be removed by hand).
- Made the old white-label restriction code do nothing, the new tier system now handles white-labeling.
- Removed the grace-period concept: no more "you can keep using your old features for two weeks after downgrade." Tier limits now apply immediately.
- Removed the pay-as-you-go tier from the system, it never gained traction and was creating confusion in the pricing page.
- Updated the public marketing pricing page to show just the simplified Starter and Pro tiers.
- Deprecated the old tiered-service-pricing model (it was never the right fit).
- Started removing the dead UI components left behind by all the no-op'd code.
Day 59 - Friday, March 6, 2026
More demolition of the old system, plus the start of a brand-new safeguard against fake admin signups.
Morning
- Removed the old feature-check function and its companion error handler from every API route, about a hundred files cleaned up.
- Added a safeguard that prevents AI agents from committing or pushing on their own. Every commit and push now requires a human in the loop, no exceptions. Quiet but important, it locks in human review for everything that lands in the codebase.
Afternoon
- Disabled per-item download buttons that no longer made sense under the new system, and made every download modal handle "payment required" errors gracefully.
- Added support for partially-paid invoices in the status type and client invoice list (so customers see a clear "Partial" badge instead of just "Paid" or "Unpaid").
- Deleted the old white-label capability matrix.
Evening
- Deleted the platform-wide tier-system feature flag (the new system is on for everyone now, no more switch).
- Deleted the old listing-limit code from the pay-as-you-go era.
- Deleted the grace-period system entirely.
- Began the Trust Gate, a new safeguard for admin signups. New business accounts now go into a pending state and require manual approval before going live, with automatic collision detection if someone tries to claim an email that's already taken.
Day 60 - Saturday, March 7, 2026
Two commits, both substantive.
- Finished the Trust Gate, the full admin signup review flow is now live. New signups land in a pending bucket, the platform team gets notified, and either approves or rejects with a reason that gets emailed back. Approved accounts are activated; rejected ones are explained.
- Continued the cleanup of the old tier-system code, more files trimmed of dead checks and no-op'd helpers.
Day 61 - Sunday, March 8, 2026
A type-system tightening day, plus an SEO upgrade for public listing pages.
Morning
- Narrowed the plan-tier values across the entire codebase to the new five-tier set so it's impossible to pass an old/invalid tier name anywhere in the system.
- Finished the Trust Gate cleanup, last few files updated with the pending-review flow.
Evening
- Cleaned up unused database tables left over from the old tier system.
- Added structured property data to branded listing pages. Search engines and link-preview tools now understand each listing as a real property listing, with price, address, photos, and details, not just a generic web page. Public listings should start showing richer previews when shared.
- Added incremental static page generation to branded listings, pages stay fast and fresh without needing a full deploy to update.
Day 62 - Monday, March 9, 2026
A superadmin-side companion to the Trust Gate.
- Built the superadmin signup-review interface. The platform team can now see every pending business signup in one place, review their details, and approve or reject with a reason that's emailed back to the applicant. Closes the loop on the Trust Gate that went live last week.
- Added the ability for a superadmin to delete an admin account when needed (with proper guardrails, soft-delete pattern, audit trail).
- More database cleanup of leftover tier-system artifacts.
Day 63 - Tuesday, March 10, 2026
A short single-commit day covering three small areas.
- Added calendar event settings for finer control over how events behave.
- Added cascade delete for admin removal so cleaning up a deleted account is consistent.
- More small tier-system housekeeping.
Day 64 - Wednesday, March 11, 2026
Calendar fixes, plus a small SEO win for public pages.
Afternoon
- Polished listing creation, the calendar sync button on the admin dashboard, and a week-view rendering case where events spanning across days were being split incorrectly.
- Made the calendar sync handle cancelled events properly (was leaving stale entries in Google Calendar).
- Tuned Stripe Connect transfers so payouts flow through cleanly.
Evening
- Improved SEO on the property pages and added meaningful alt text to gallery images so they're accessible and indexable.
Day 65 - Thursday, March 12, 2026
A heavy infrastructure day that touched storage, billing, the analytics dashboard, the enterprise console, and email content. Each change small in description, big in impact.
Morning
- Began moving listing media to long-term archive storage, original full-size files are now offloaded to the cheaper, slower archive layer once they've been delivered, freeing the fast layer for current work.
- Switched storage handling on the Pro tier from hard-blocking to overage billing. Pro customers no longer get blocked when they exceed their storage limit, instead, the overage rolls into their next bill at a per-gigabyte rate. Practical for working photographers who don't want a job blocked because they're over by 5 GB.
Afternoon
- Wired the just-archived video files to serve directly from the archive layer with no perceptible difference to the customer.
- Built the enterprise management console, the superadmin's tool for managing high-volume enterprise customers as a distinct group, with their own dashboards and settings.
- Added real revenue data to the analytics dashboard. The "Revenue Collected" metric and its breakdowns now pull from actual payment records, not estimated values.
- Eliminated the photo derivative pipeline. Instead of generating multiple resized copies of every uploaded photo (small, medium, large, thumbnail) and storing them, the platform now generates the right size on-demand at the edge of the network. Faster uploads, less storage, identical-looking output.
- Added an unbranded page link to the listing-published and listing-delivered emails so customers can grab it without hunting through the admin.
Day 66 - Saturday, March 14, 2026
A storage and AI-roadmap day.
Morning
- Built a storage management page with a per-listing breakdown so business owners can see exactly which listings are eating their quota and clean them up directly.
- Made bulk delete also remove the original archive copies (was leaving them stranded in storage), with a guard that prevents accidentally deleting delivered listings.
- Drafted a roadmap for an AI Agents platform.
- Wrote up the production setup guide for Stripe and tightened an import source key constraint.
Evening
- Hardened the storage proxy with proper secret validation.
Day 67 - Monday, March 16, 2026
A course-correction day on the photo derivative pipeline.
Two days ago the platform switched to generating image sizes on demand at the edge, eliminating the pre-generation pipeline. In real-world use a few cases needed pre-generated copies after all (some client devices wouldn't accept the on-demand format, certain bulk operations relied on derivatives existing). The fix: keep both, prefer pre-generated when available, fall back to on-demand when not.
- Re-enabled background derivative generation for new image uploads.
- Re-enabled derivative generation for Dropbox and Google Drive imports.
- Updated the public display logic so it prefers pre-generated sizes and only falls back to on-demand when needed.
- Built a cleanup tool to reclaim space from stale derivatives that no longer have a parent file.
Day 68 - Wednesday, March 18, 2026
A quiet doc-and-config day.
- Consolidated billing documentation, archived completed plans, and walked through environment-variable setup for production.
Day 69 - Thursday, March 19, 2026
The day the new billing layer came online. Credits, ACH, unified billing history, and a customer-side wallet all shipped.
Morning
- Locked in final pricing across every product (subscriptions, CopyPro, AI editing, storage add-ons).
- Created every Stripe product (live mode and sandbox) so checkout flows can hit real prices.
- Enabled all payment methods in the Stripe dashboard (cards, Apple Pay, Google Pay, Link, ACH).
- Tightened admin-account ownership so payment records always land on the correct business.
- Turned on Stripe Tax for automatic sales-tax calculation.
- Hardened the webhook handlers.
Afternoon
- Built the credit system. Customers now have a credit balance they can pre-purchase and spend on AI photo editing, image protection, and other usage-based features. No more "enter your card every time you click enhance."
- Redesigned CopyPro around the credit model. Image protection now runs on credits with clearer messaging and a single Protect button per gallery. Removed a confusing "unlimited" tier row that nobody chose.
- Added ACH (bank transfer) as a supported payment method end-to-end: settings, checkout, webhook handling.
Evening
- Built the client wallet page with credit balance, a purchase slider, and a full purchase history.
- Added a credit balance card to the admin subscription settings.
- Built unified billing history so every charge, refund, credit purchase, subscription renewal, and ACH transfer appears in one chronological view per business.
- Routed billing history writes to fire on payment success (not on attempt) so the history only shows real money movements.
Day 70 - Friday, March 20, 2026
The day AI photo editing started actually billing.
The infrastructure for AI editing has been around since January (Day 19). The credit system shipped yesterday. Today the two were wired together, plus a redesign of how galleries appear on the customer side.
Morning
- Wrote the integration plan for connecting AI editing to credit billing.
- Resolved three blockers in the AI editing pipeline: refined admin lookup, corrected source URL routing, and updated a payment-method check to use the credit-balance system.
- Sorted out the AI provider's two-step upload flow so big files transfer reliably.
- Wired the AI edit modal to actually poll for the real job status and surface the approval flow when results are ready.
Afternoon
- Built proper video thumbnails. Every video now generates a poster image at the 3-second mark by default, with the option to upload a custom thumbnail.
- Redesigned the galleries view on the client portal as a card grid with thumbnails. Customers see what's in each gallery at a glance instead of a flat list.
- Polished the gallery cards: CDN-backed thumbnails, responsive grid, query optimization.
Evening
- Made AI editing async. Submit a batch, close the modal, get a notification when the work is done, instead of having to sit and wait at a spinner.
- Replaced the "add a payment method" gate with a "do you have enough credits?" check, completing the credit-system integration.
Day 71 - Saturday, March 21, 2026
A long, prolific day. Subscription downgrades got a real flow, the new Galleries module started shipping, and bulk AI enhancement landed.
Morning
- Wrote a clean policy for subscription downgrades and built the flow to match it: downgrades schedule at the end of the current billing period (no surprise mid-cycle drops), no refunds for the unused portion.
- Added a "downgrade scheduled" banner that shows up while a downgrade is pending so it's never a surprise.
- Started showing credit cost and current balance directly in the AI edit modal so customers can see what an action will cost before they confirm.
Afternoon
- Hid the Change Plan button for enterprise customers (their plans are managed by the platform team directly).
- Locked in the AI editing pricing model.
Evening
- Started shipping the Galleries module. This is the project that came out of the listings-to-galleries rewrite. The first piece: the container shell with tabs, type configuration, and settings, so different gallery types (portfolio, listing, event, family session, etc.) can plug in cleanly.
- Updated the marketing pages to reposition the platform around galleries - useful for any kind of professional photographer, not just real estate.
- Added a bulk AI enhance button right in the gallery selection bar.
- Made AI enhancement auto-apply by default so customers don't have to manually approve each result one at a time.
- Built a confirmation modal for bulk enhancements so a customer doesn't accidentally enhance a whole gallery and burn their credits.
- Made action buttons context-aware: the buttons that show up depend on what's selected and what state those items are in.
- Started Portfolio Gallery Phase 2 - the creation form, the section editor, and the underlying API.
Day 72 - Sunday, March 22, 2026
A short day, mostly polish on yesterday's work.
- Iterated on the AI Enhance button color twice before settling on a slate tone that survives the production CSS purge.
- Built portfolio project image uploads so a portfolio section can hold a real set of images (not just metadata).
Day 73 - Monday, March 23, 2026
Cloud imports and video support landed for portfolio galleries, plus a gallery theme system on the public side.
Morning
- Wired Dropbox and Google Drive cloud imports into portfolio galleries. Customers can now pull photos straight from their existing cloud storage instead of re-uploading them.
- Added video support to portfolio galleries.
Afternoon
- Built the activity log and analytics for portfolio galleries.
- Cleaned up the cloud picker so thumbnails show in the file browser, and unified the various "Add Images" modals into one consistent component.
- Corrected the pricing display on the public portfolio page (a dollars-vs-cents conversion adjustment).
Evening
- Added an ACH payment toggle for invoices and wired Stripe Connect webhooks to handle invoice payments through the same pipeline.
- Built the public gallery theme system with proper video support on the public pages.
Day 74 - Tuesday, March 24, 2026
A long day spanning two big projects: the invoice settings overhaul and continuing portfolio gallery polish.
Morning
- Fixed invoice reminder timing and the public invoice status badge.
- Started showing the payment method on paid invoices (card brand and last 4 digits) so customers and business owners can both confirm what was used.
Afternoon
- Built the invoice settings sidebar. A new dedicated area for the per-business invoice rules (reminders, recipients, payment methods, etc.) instead of cramming them into a single settings page.
- Built the gallery theme scaffold with two starter themes (Nova and Comet).
- Added BCC support for invoices so business owners can copy a partner or accountant on every invoice.
- Added per-invoice reminder overrides so the standard schedule can be tweaked per customer.
- Added per-invoice payment method overrides and a per-invoice partial-payments toggle.
- Built a draft preview for portfolio galleries with inline video playback.
Evening
- Added customer action toggles to the invoice settings: tipping, save-payment-method-for-next-time, and goods-receipt confirmation can all be turned on or off per business.
- Added video title and custom thumbnail editing to portfolio galleries.
- Made portfolio video thumbnails auto-extract on upload using the same pattern as listing videos.
Day 75 - Wednesday, March 25, 2026
A short polish day for the new gallery and invoice systems.
Afternoon
- Added the poster attribute to every video player so thumbnails actually show on the video element instead of a black square.
- Refined the toggle switch styling and defaulted partial payments to off (safer default).
- Removed a redundant "Payment Method / Add Card" panel from the subscription settings (the credit system + saved methods elsewhere made it duplicative).
Evening
- Added thumbnails to cloud import results, built a video thumbnail selector, and added gallery delete.
Day 76 - Friday, March 27, 2026
A security-hardening day, with email and calendar polish around it.
Afternoon
- Polished public order emails, calendar email timezone handling, and the way event durations show up in the sidebar.
- Wrote up a stack of project findings docs and an admin-ops assistant guide.
Evening
- Removed debug and test API endpoints that had been left in production. Some of them were technically locked behind a development-mode check, but having them deployed at all was risky.
- Hardened the file proxy against server-side request forgery, added rate limiting, and locked down which sites are allowed to embed it.
- Replaced an over-broad user lookup in the client invitation flow with a targeted profile lookup so accounts can't be enumerated.
- Built proper authentication wrappers for superadmin and client API routes so every endpoint goes through the same gate.
Day 77 - Saturday, March 28, 2026
The biggest day of the week. Calendar got a major upgrade, email cleanup landed, and a stack of security findings closed.
Morning
- Polished the order confirmation email: corrected reply-to addresses, added a pending status badge, expanded the services breakdown, and de-duplicated the send pipeline.
- Cleaned up the listing activity log: removed reorder noise, polished the email-clicked event, started tracking downloads.
Afternoon
- Switched the email webhook signature checks to a constant-time comparison method, the industry-standard approach.
- Made the security scan cron require a secret, no exceptions.
- Aligned calendar event durations with the actual services on each job and added services, square footage, and price to the calendar sidebar.
- Added admin scoping to the bulk listing operations so a business can only act on its own data.
- Hid Square and PayPal from the payment UI. The platform consolidated on Stripe for all payments - those two are still wired in code but no longer offered as options.
Evening
- Major calendar upgrade. Added an event detail modal with action buttons, ICS calendar invites that work in any calendar app, richer Google Calendar enrichment, and status-based event colors (pending = orange, confirmed = blue) so the calendar reads at a glance.
- Added duration adjustment directly in the reschedule modal so changing a session length doesn't require leaving the calendar.
- Added an inline Add Time adjuster in the event detail modal.
- Added HTML sanitization to the marketing contact email templates (so a malicious form submission can't inject content).
- Replaced in-memory rate limiting with database-backed rate limiting on four public endpoints. The old approach reset on every server restart; the new one persists.
- Hardened the public order form's availability check so two customers booking the same slot at the same time can't both succeed.
- Removed the last few places where prices were being calculated locally instead of through the central pricing engine.
Day 78 - Sunday, March 29, 2026
A focused day on calendar accuracy and editor-portal hardening.
Morning
- Wired buffer time into the availability engine. Buffer time was being saved in the admin's calendar settings but never actually applied to availability checks. Now it is - back-to-back bookings respect the buffer.
- Added error and loading boundaries to the editor and photographer portals so a single bad response can't crash the whole portal.
- Made booked time slots duration-aware - a 90-minute shoot now blocks 90 minutes, not the next "default slot," so the calendar honestly reflects what's bookable.
- Added a max-daily-events setting so a business owner can cap how many shoots land in one day.
- Added a server-side authentication check on the superadmin layout as a defense-in-depth measure on top of the middleware check.
Day 79 - Monday, March 30, 2026
A short single-commit day.
- Made the bot-verification step optional on the email-code resend so legitimate customers don't fail it twice in a row when they need a fresh code.
Goal status - March - Tier system finalized: the new five-tier model rolled into production, the old tier code dismantled, Free tier locks went live with backend enforcement, payments fully consolidated on Stripe (Square + PayPal hidden). Invoicing got a full audit pass with public invoice pages, payment receipts, partial payments, and email tracking. The order and scheduling systems were tightened end-to-end. Carrying into April: the platform-level email inbox still has pieces to finish, and AI editing's billing integration shipped in-month but later than planned (landed March 20).
Day 80 - Wednesday, April 1, 2026
Goal for the month: Take everything live with real clients. Every process the founding photography business runs, orders, scheduling, invoicing, delivery, reschedules, emails, AI photo editing, flows through the platform, with their actual customers interacting on the other side. The proof that the platform is 100% working and that an admin can run their entire business through it.
The start of April. The first batch of work was unwinding a Stripe Connect onboarding deadlock and reworking the public payment button.
Morning
- Smoothed out Stripe Connect onboarding - business owners who started but didn't finish setup can now resume from where they left off, or disconnect and start over.
- Added Stripe Connect domains to the security policy so the onboarding screen actually loads.
Afternoon
- Added platform transaction fee terms with explicit consent enforcement - the cut the platform takes on each payment is now clearly disclosed and acknowledged at signup.
- Made Stripe status checks resilient to API hiccups so the onboarding state doesn't get stuck on a transient error.
Evening
- Stopped creating duplicate Stripe Connect accounts when a business retried setup - now relinks the existing one.
- Added the missing payment-processor entry that ACH checkout was looking for.
- Replaced the credit-card / ACH selector with a single branded "Pay Securely" button on customer checkout. Less choice, less friction, more conversion.
Day 81 - Thursday, April 2, 2026
A long workshop day. Calendar reminders got a precision rewrite, error boundaries went up across every public surface, and a stack of security audit findings closed.
Morning
- Redesigned calendar reminders to send at precise scheduled times instead of "sometime in the next hour." Reminders now arrive when the customer expects them.
- Closed a batch of small but real polish items: missing dependencies on data-loading effects, member-type verification on the editor and photographer portals, validation on the calendar add-event modal, a duplicate API barrel file in services, and superadmin sidebar highlighting the right parent nav item on sub-routes.
Afternoon
- Added error boundaries to high-traffic admin routes so a single bad render can't blank an entire page.
- Tuned the calendar-reminder background work down to a safer concurrency level so it doesn't bump into platform limits.
- Fixed three storage edge cases: a delete that crashed on empty paths, archived listings stuck on "Processing" when on-the-fly transforms were enabled, and listing activation now always pre-generates derivatives instead of conditionally.
Evening
- Added error boundaries to the client, public, and marketing routes as well, so every part of the site catches its own errors gracefully.
- Built a root not-found page and a global-error page so unknown URLs and unexpected crashes always land somewhere branded instead of a default error screen.
- Added noindex on the public invoice page (it's per-customer, shouldn't be in search results).
- Added basic SEO metadata to the marketing contact page.
- Tightened up validation on the password-reset and forgot-password pages so a malformed parameter can't lead anywhere unexpected.
- Standardized the LOTULIS logo alt text everywhere it appears.
- Replaced default Tailwind grays with proper design tokens on the join page so it stays brand-consistent.
- Limited a public download query to specific columns instead of selecting everything (less data crossing the wire, less surface area for accidents).
- Switched encryption from a hardcoded salt to a per-record random salt so two records with the same plain text encrypt differently.
- Switched two webhook signature checks to constant-time comparison, the industry-standard approach.
Day 82 - Friday, April 3, 2026
The day pricing math left the rest of the codebase entirely.
The pricing engine has been the official source of truth since January, but a handful of local fallbacks and direct calculations had been sneaking in over the months. Today every one of them was hunted down and replaced.
Pre-dawn
- Reduced the editing-session cleanup job from hourly to every six hours.
- Routed invoice line items through the pricing engine's subtotal so they always match the order they came from.
- Replaced a weak hashing function with a proper cryptographic one in the security scan fingerprints.
- Cleaned up six unused components and a bundle of stale type files that no longer matched the database.
- Added a missing color token (--color-warning) that some warning states were silently falling back on.
- Used proper accessibility roles on toast notifications so screen readers announce errors as alerts and successes as status updates.
Morning
- Created database migrations for findings from a long-running internal review.
- Added all project documentation to version control (it had been living in scattered places).
- Wrote a manual testing checklist for the post-review remediation pass.
Afternoon
- Improved touch-device hover and long-press behavior in the gallery for iPad and iPhone, and quieted a stack of notification API responses (406/404).
- Removed the per-listing storage cap. Storage is now a per-business resource, not divided up listing-by-listing - much friendlier for any business that occasionally has a big shoot.
- Added proper touch drag-and-drop to the image gallery so reordering works on tablets.
- Audited and archived completed planning docs to keep the project tree clean.
Evening
- Removed the last local pricing math from the codebase. Job detail page, client order form, invoice line items: all now go through the engine. Penny-rounding bugs and price drift become impossible.
- Added foreign-key constraints to the bundle items table so a deleted service can't leave dangling references.
- Added a validation boundary to the new-order page so a malformed search parameter can't crash the page.
- Hardened PayPal webhook signature verification to fail closed if any check is missing or invalid.
- Removed three unused dependencies (a date library, a zip library, a payments client) since the platform consolidated on others.
- Added masking for personal information in production email logs so logs never contain raw email addresses or phone numbers.
Day 83 - Saturday, April 4, 2026
A cleanup day. Lots of small corrections, an old pricing fix on the marketing site, and a mass archive of completed documentation.
Morning
- Added a script to verify foreign-key integrity on the bundle items table.
- Started a build and lint warnings tracker so quality regressions don't sneak in.
- Corrected the RawVault storage pricing on the marketing page from $0.01 to $0.02 per gigabyte per month, matching the actual platform cost.
- Removed two deprecated migration packages ($299 and $599 flat-rate options) that were no longer offered.
- Marked the post-launch features list current.
Afternoon
- Created a single unified backlog for all deferred work so nothing falls between the planning docs.
- Added input validation directives to the project rules so every form going forward gets the same treatment.
Evening
- Mass archive of completed planning docs, design systems, integration guides, and stale checklists. The active docs tree is now lean and current.
Day 84 - Sunday, April 5, 2026
A short doc-cleanup day.
- Archived more checklists and the older feature matrix, reorganized the email-setup notes, and removed obsolete files.
Day 85 - Monday, April 6, 2026
The day travel-time-aware calendar scheduling went live.
This had been on the design board since March (Day 54). Today it landed in production.
Morning
- Added an "expired" status to the payment status options and made a security scan field nullable so older records validate correctly.
Evening
- Built travel-time-aware calendar scheduling. When a business owner is about to book a shoot, the calendar now checks the drive time from the previous shoot's location to the new one and refuses bookings that don't physically fit. No more accidentally double-booking a 3 pm in one town and a 4 pm forty minutes away.
- Wrote the testing guide for the travel-time feature.
- Fixed configuration so production environment files stay out of version control.
- Closed four travel-time safety gaps, edge cases where the original logic could either approve an impossible booking or reject a legitimate one.
Day 86 - Tuesday, April 7, 2026
A heavy travel-time follow-up day. Yesterday's launch surfaced edge cases everywhere it touched, today they got fixed, plus a few unrelated polish items.
Morning
- Added a "suggest alternative day" mode for travel-time conflicts: instead of just refusing the booking, the system can suggest a nearby day that fits.
- Added a public-profile toggle so business owners can choose whether to show their business address publicly.
Afternoon
- Made the travel-time check graceful when the maps service is slow or unavailable, falls through to a normal availability check instead of locking up the whole calendar.
- Wired minimum booking notice into the availability engine. A business owner saying "no bookings within 24 hours" now actually means it.
- Wired up every calendar setting that had been saving but not actually being applied (a familiar pattern this month).
- Stripped internal travel-time messaging from the client-facing order form, clients don't need to see the engine's reasoning.
Evening
- Rebuilt the server-side travel-feasibility check with a corrected time format so dual-checks (client + server) actually agree.
- Enforced minimum booking notice and maximum advance days on the client-side date picker so impossible dates can't even be selected.
- Made sure booked time slots still come back to the client even when travel time requires an address.
- Awaited email sends so they can't fire-and-forget into the void.
- Refined order confirmation from the edit page so it sends a single email.
- Built server-side double-booking prevention for every order path. Even if the client form misbehaves or someone races a request, the server refuses to take both bookings.
Day 87 - Wednesday, April 8, 2026
The day listing auto-enrichment shipped, plus two important calendar improvements.
Morning
- Built listing auto-enrichment. When a business owner adds a property address, the platform automatically pulls property data (square footage, bedrooms, bathrooms, year built, lot size, etc.) from a real-estate data provider, then uses AI to generate a polished property description. The admin gets a full listing draft instead of a blank form.
- Added per-listing enrichment controls so a business owner can opt in or out per property and removed price from the auto-enrichment (admins want to set their own).
Afternoon
- Made services, square footage, and notes flow into calendar events automatically when an order is bulk-confirmed.
- Synced services and descriptions to the calendar event whenever an order is saved.
- Started showing travel time between consecutive appointments on the admin calendar and synced it to Google Calendar so the buffer between shoots is visible everywhere a business owner looks.
Evening
- Isolated the auto-enrichment from listing creation, if enrichment fails, the listing still saves cleanly.
- Tightened input validation and error handling on the enrichment endpoint.
- Replaced the hardcoded 90-minute calendar event duration with the actual sum of service durations so calendar blocks reflect real shoot times.
Day 88 - Thursday, April 9, 2026
The day the support ticketing system went complete and the order confirmation flow got rebuilt to use a single shared path.
Morning
- Fixed security scanner endpoint paths that had drifted and quieted down noisy logs.
- Built a daily background job that recalculates travel time on calendar events so distance estimates stay current.
- Replaced a legacy email column with the proper modern columns across the codebase.
Afternoon
- Built request-level user caching so the same authentication check inside a single request doesn't fire multiple database queries.
- Shipped the complete support ticketing system. Ticket notifications (in-app pings and email), real-time updates on ticket views, response templates for the superadmin team, SLA tracking, and satisfaction ratings.
- Cleaned up a stack of completed planning docs.
Evening
- Added an "auto-confirm" checkbox to the admin new-order form, orders can be marked confirmed at the moment of creation.
- Rebuilt the order confirmation flow so all three paths (admin new order, admin edit page, bulk operations) flow through the same shared logic. Side effects (listing creation, calendar event, notifications, emails) now fire identically regardless of which path was used.
- Added a Create/View Listing button on the order edit page so admins can jump between order and listing without losing context.
- Made order notes flow into the calendar event description.
Day 89 - Friday, April 10, 2026
The day a systematic database audit started, plus two security upgrades from upstream dependencies.
Morning
- Repaired schema drift on the calendar reminders table (a column had quietly been added without going through the formal migration process).
- Added grace windows to the reminder scan so reminders aren't missed when the scan runs slightly late.
- Started a code-vs-database audit framework. A formal walk through every table comparing what the code expects to what the database actually has, catches the kind of drift that's accumulated over months of fast iteration.
- Audited and fixed drift on five tables in the first morning session: listings, activity log, clients, admins, and the activity-log indexes.
Afternoon
- Validated iframe embed URLs at the server to prevent malicious URLs from being injected into property pages.
- Refined the enrichment flow so navigating away mid-process keeps the AI work running.
- Audited and repaired drift on the billing events and contracts tables.
Evening
- Bumped a critical dependency to the latest version with the most current security posture.
- Bumped the web framework for CSRF and denial-of-service fixes.
- Replaced strict single-row queries with permissive ones in places where missing data is legal, silenced a stack of harmless 406 errors that had been polluting the logs.
Day 90 - Saturday, April 11, 2026
The day the gallery rewrite started in earnest, plus CopyPro Stripe checkout and another big audit pass.
Morning
- Continued the database audit, closing items on the email settings and platform connections tables.
- Tightened up access rules on a settings table so only authenticated users can read it.
Afternoon
- Wired CopyPro to a real Stripe checkout end-to-end. Customers can now buy CopyPro tier upgrades directly through the platform with proper subscription management.
- Cleaned up duplicate billing records that had been double-writing during the credit system rollout.
Evening
- Consolidated twenty-five per-table "updated at" trigger functions into a single shared one, small but a meaningful chunk of dead code removed.
- Started the portfolio gallery rewrite. This is the rip-and-rewire to migrate the old listing-shaped portfolio code onto the new universal galleries foundation. The schema went in tonight, the old code got quarantined, and the first wiring pass landed on the portfolio detail page.
Day 91 - Sunday, April 12, 2026
A short Sunday focused on shipping the public service API.
Evening
- Continued the database audit, closing items on calendar event notes and cleaning up bundled-service records.
- Built the public service API. External tools and partner systems can now read a business's services catalog and create or update orders programmatically. First customer of this API is an automation a partner is building.
- Tuned the services endpoint to handle every related-record lookup cleanly.
Day 92 - Monday, April 13, 2026
A long, multi-thread day. Seven new portfolio gallery themes shipped, the orders system gained draft mode, calendar event editing became standalone, and a public order status page went live.
Morning
- Built seven distinct portfolio gallery themes with a theme selector UI and a full page-style system: dark mode, hero variations, alignment, density. Each theme has its own personality (Nebula, Aurora, Eclipse, Constellation, Solstice, and others).
- Rewrote four themes with truly distinct layouts and added a background color picker so customers can match their brand.
- Polished the masonry layouts: real image dimensions detected client-side, last row fills evenly, JavaScript-based column distribution where CSS columns weren't cutting it.
- Switched some themes to use full-resolution image URLs so high-DPI displays render sharp instead of upscaled.
Afternoon
- Added a "draft" status to orders. Business owners can now save an order partway through without triggering all the side effects (no listing creation, no calendar event, no client emails). Confirm later when ready.
- Built a public order status page where customers can self-serve: see their order, request a reschedule, request added services.
Evening
- Made the reschedule form respect each business's calendar settings (working hours, buffers, advance notice).
- Wired client reschedule and add-service requests into proper admin notifications and visible request displays on the order.
- Made calendar event clicks open the detail modal directly (replacing a side-drawer that interrupted the flow).
- Added client change/add directly inside the event detail modal so admins don't have to leave the calendar.
- Built standalone event editing: services, notes, and even creating an order from a blank calendar event.
- Added a cascade-delete modal that shows every linked record (calendar event, listing, invoice, etc.) before an order is deleted, so business owners know what they're removing.
Day 93 - Tuesday, April 14, 2026
The day multi-session orders went live.
A "multi-session order" is one that spans multiple shoot dates - exteriors on Tuesday, interiors on Wednesday, sunset shots on Friday. Until today the platform treated this as separate orders. Now it's one order with multiple sessions.
Morning
- Built the foundation: data model, calendar integration, the way one order can carry multiple shoot dates with different services on each.
- Built the order form for creating multi-session orders.
- Each session can pick its own services from the business's catalog OR use a free-text label.
- Wired in proper date and time pickers for each additional session.
- Shipped the customer-facing pieces: the public order page shows all sessions, customers can see what's happening when, calendar events stay in sync.
- Audit corrections: timezone handling and email content for multi-session orders.
Afternoon
- Continued database audits: closed items on client invitations and client custom pricing, dropped a dead segment-assignment table, finished the client-segments audit including a security finding.
- Created a schema-guide template so future audits produce consistent documentation.
Day 94 - Wednesday, April 15, 2026
Multi-session polish day, with one notable new feature: a draft mode on the public service API.
Morning
- Polished calendar events so they show the actual service names.
- Added per-session service multi-select to the order form so each session can carry its own service list.
- Made sure all multi-session events get created before the order's confirmation email goes out, so the email correctly lists what's happening on each day.
- Order confirmation emails now show services per session.
- Public order pages now show services per session too.
- Polished the timezone handling on multi-session emails.
Afternoon
- Added a draft mode to the public service API. External agents creating orders on a business's behalf can now create them as drafts that the business owner reviews before confirming. Default behavior was flipped: API-created orders now require explicit confirmation rather than going live automatically.
- Updated the catalog/search experience inside the order form so finding the right service across a long list is easier.
- Added a prominent "This Session" callout inside the calendar event detail modal so admins always know which session of a multi-session order they're looking at.
Evening
- More audit work: clients table audited (two security findings closed), cloud integrations audited, calendar events re-audited after the multi-session changes, comments table audited.
Day 95 - Thursday, April 16, 2026
The order edit page got a full overhaul, plus a few small but meaningful fixes for client and team management.
Morning
- Audited the contracts table (a future-feature placeholder, no drift to fix).
- Made password optional when an admin creates a client. Admins can now add a client without forcing a password - useful when the client will set their own later via the invitation link.
- Order confirmation toasts now show the client's name instead of just "order created."
Afternoon
- Rebuilt the order edit page: full pricing engine, all status options available, tier dropdowns, every field consistent with the new-order form. The edit page had been lagging behind for months.
- Wired the edit page directly to the central pricing engine so pricing math is identical to the new-order page (no more drift between create vs edit).
Evening
- Built an order completion modal with proper admin email confirmations on completion.
- Made the team-invitation flow await the email send and surface delivery status to the admin (so the admin knows whether the invite actually went out).
- Polished the invoice dropdown menu so it's never clipped at the bottom of long lists.
Day 96 - Friday, April 17, 2026
The day the platform went multilingual.
Afternoon
- Blocked client deletion to protect order history (clients with past orders can no longer be hard-deleted; soft-delete only).
- Added team members to clients for email delegation (a client can have multiple people on their team, each receiving notifications).
- Soft-deleted team members instead of hard-deleting them - keeps the audit trail intact.
- Updated the migration access rules to check the correct table for admin lookup.
Evening
- Added Vietnamese language support to the editor portal, the first non-English language on the platform. Photo editors who work in Vietnamese can now read every screen, every button, every notification in their language.
- Translated every editor-portal page to Vietnamese, end-to-end.
- Polished post-creation flows for team assignment, session management, and storage controls.
Day 97 - Saturday, April 18, 2026
A short day on a Saturday - two small polish items for the editor session UI.
- Polished the editor assignment dropdown so it closes when you click outside it.
- Refined async behavior in the client team section so handlers always fire on fresh state.
Day 98 - Sunday, April 19, 2026
A long, prolific Sunday. Listing themes shipped, invoicing got a big overhaul, and a unified "cascading action" modal landed.
Morning
- Validated embedded JPEGs inside RAW files before processing, so a corrupted preview can't crash a session.
- Refined the broker creation and client update forms for cleaner submission.
- Tightened the storage counter: deleting an editor session's files now decrements the storage usage in real time.
- Restricted subscription checkout to credit cards only (ACH and bank-redirects don't fit the immediate-charge model for plan upgrades).
- Added a congratulations email when a customer upgrades their plan.
- Shipped the first luxury editorial property listing theme (originally called "Syd," renamed to "Nicko" mid-day).
Afternoon
- Built the listing theme selection system - business owners can now pick from multiple property-page styles per listing, not just one default look.
- Updated 25 dependencies to current versions flagged by automated audits.
- Updated the job-conflict query to use the correct column, and quieted a stale calendar-event delete path.
- Built sort, job search, and page size controls for the invoice list.
- Allowed editing and resending of sent or overdue invoices - previously a sent invoice was locked, which didn't match how real businesses handle billing corrections.
- Built a delivery confirmation modal and tuned the timing on order completion so the email goes out at the right moment.
- Distinguished "Delivered" from "Completed" labels on listings (a delivered listing is still active; a completed one is fully wrapped up).
Evening
- Added invoice email preview with custom message and BCC support.
- Built a cascading action modal: when a single action (delivering a listing, completing an order) needs to fire multiple side effects (send delivery email + auto-create invoice + send invoice email), it's now one consolidated flow with one combined email instead of three separate things firing in sequence.
- Wired the cascading modal into every entry point that triggers cascading actions.
- Fixed invoice tax math: discounts now reduce the taxable base instead of being treated as an add-on, and the database trigger calculates tax after discounts (matching how real-world tax actually works).
- Added architecture decision records (ADRs) to the docs so future-me knows why certain choices were made.
Day 99 - Monday, April 20, 2026
The day invoice pricing was fully unified with order pricing.
Morning
- Unified invoice pricing through the single calculation engine that orders already use. Invoices and orders can never disagree on totals again.
- Recalculate totals on manual price or quantity changes inside the order edit form.
- Clear stale discounts when a service is removed from an order (no orphan discount lines).
- Address Codex review findings on order edit totals.
Afternoon
- Quick edit syncs the invoice when the order changes, preserving the taxable flag on each modifier.
- Respect each modifier's taxable flag instead of treating all discounts as taxable.
- Made all discounts reduce the taxable base.
- Sync invoices when an order is edited at any status except paid (paid invoices stay frozen).
- Ran a database audit that found one of the CopyPro tables doesn't actually exist in production - flagged for follow-up.
- Added an atomic guard on invoice total updates so a race between a payment-arrived webhook and an admin-edit can't corrupt totals.
Evening
- Replaced a "Pending" invoice card with a "Draft" card showing draft totals so the dashboard reflects work-in-progress invoices.
- Added search to the mobile admin header with autofocus support.
- Hardened the pricing engine: immutable inputs, NaN protection, dead-code cleanup.
Day 100 - Tuesday, April 21, 2026
Day 100. A polish-heavy day with one important safety fix and a new dashboard.
Morning
- Pre-filled the cascading action modal's email subject and message based on the action being taken - no more typing the same things repeatedly.
- Made the mobile admin sidebar scrollable with a pinned "Add" button so the new-thing creation menu is always reachable.
Afternoon
- Show a
$indicator on a listing's "Completed" badge when its invoice has been paid - a quick visual signal that the money side is done. - Polished the Add-dropdown so it never clips on the mobile sidebar.
Evening
- Hard-blocked all emails and notifications for draft orders. Drafts are invisible to clients - nothing fires until the draft is explicitly confirmed.
- Built dashboard widgets for "Today's Schedule" and "Reminders" - the home page now opens with what the business owner actually needs to see today.
- Always show email customization on deliver and redeliver actions.
- Set invoice due date to today by default ("due upon receipt") for businesses that bill at delivery.
- Save and load comments per invoice line item so the admin can leave notes that survive across edits.
Day 101 - Wednesday, April 22, 2026
The day signup got smart.
Until today, signup required a new business owner to type in everything: business name, address, services, hours, social links. Today the platform learned to read a public website and fill in most of that automatically.
Morning
- Cached real-estate property data lookups so duplicate API calls don't fire on the same address.
- Added date validity and day-of-week restrictions to service modifiers (a "weekend rate" or "summer surcharge" can now be defined directly).
Afternoon
- Genericized the signup language so it's not photography-specific anymore. "Business" instead of "studio," "service" instead of "shoot."
- Added a
business_extractedtable and onboarding tracking columns to capture what the platform learns about a new signup. - Built AI business extraction during email verification. When someone signs up with a business email or website, the platform fetches their public site, reads it with AI, and extracts business name, services, hours, locations, social links.
- Added multi-page extraction so the AI doesn't just see the homepage; it reads the about page, services page, and contact page.
- Built the onboarding wizard that shows the new business owner what the AI extracted and lets them confirm or correct everything before the account is created.
- Added a live progress checklist that updates in real time while the AI is reading the website.
Evening
- Added on-demand URL extraction for signups that come in with a generic email (no website hint), so a business owner can paste their site URL and trigger extraction manually.
- Built a multi-tier website extraction strategy: try direct fetch first, fall back to Google's cached version, fall back to a headless-browser approach.
- Added a click-to-preview side panel on the orders list - click any order and see its details without leaving the list view.
- Polished a handful of small items: Stripe Connect lookup paths, the calendar date range query, and the data flow on listings pages.
Day 102 - Thursday, April 23, 2026
A focused day with two real wins: drag-resize calendar events and custom invoice line items.
Morning
- Removed an unused browser-automation dependency that was bloating the deploy size.
- Wired custom message and BCC support through standalone delivery emails (matching the cascading delivery flow).
- Updated the entitlements check inside API routes to bypass the cache so storage limits stay accurate even right after a usage change.
Evening
- Added custom line items to orders. Business owners can now add an arbitrary line item to an order (one-off charges, custom rush fees, ad-hoc additions) and it flows through the pricing engine and into the invoice cleanly.
- Built drag-and-drop event editing on the calendar. Drag an event to move it to a different day or time. Drag the edges to resize it. The schedule reflows itself; notifications and side effects fire as expected.
Day 103 - Friday, April 24, 2026
A multi-thread day. Reminders got a major upgrade, social publishing went live, and the security/vendor documentation got a big lift.
Morning
- Stopped firing notifications for admin-blocked calendar time (vacations, personal commitments - not customer-facing).
- Tightened the payment-email path so a webhook race can't double-send the receipt.
- Documented the vendor security inventory per the standard checklist for vendor security audits.
- Migrated the photo derivative system from listing-specific to a generic parent reference, so galleries can use the same pipeline.
Afternoon
- Polished the dashboard reminder card to wrap long text instead of clipping it.
- Wrote a P1 incident runbook and added a folder for vendor data-processing agreements.
- Major reminders upgrade: added a notes field, color-coded categories with filtering, team-member assignment with cross-tenant access rules, file attachments (images and PDFs), and a paste-to-create flow that turns a clipboard image into a reminder card.
Evening
- Launched social publishing. Business owners can now connect Facebook (and Instagram), and TikTok, and publish a listing's photos and video directly from the listing page to those platforms in one action.
- Built the OAuth connection flows for Facebook and TikTok, the publisher services for each platform, and the social-publish dialog wired into the listing actions menu.
- Added a Settings UI for connecting and managing those social accounts.
Day 104 - Saturday, April 25, 2026
A quiet Saturday. One CI tweak, no customer-facing changes.
- Excluded internal audit and archive folders from the automated-scan checks.
Day 105 - Sunday, April 26, 2026
Order-invoice sync, email deliverability improvements, and a polish pass on the order edit page.
Morning
- Added inline-edit-on-double-click to reminders.
- Built bidirectional sync between orders and invoices with loop prevention. Edit either side and the other follows; neither path can trigger an infinite update cascade.
- Wired the cancellation email's reschedule button to land directly on the customer-facing order status page.
- Made invoice notes show up in the client-facing email and corrected a misleading label on the invoice.
Afternoon
- The dashboard now uses each event's actual scheduled time rather than the raw job time, so reschedules are reflected accurately.
- Improved email deliverability for listing emails with proper branding, a clean signature, and authentication tightening.
- Added branding to the combined delivery+invoice email so it visually matches the rest of the customer-facing emails.
- Replaced a contradictory footer on client-facing emails so the small print actually matches the company.
- Removed the inline "Quick Edit" panel from the orders list - the full edit page is now powerful enough that the inline shortcut was redundant.
- Replaced "View" with "Edit" in the orders list - same destination, clearer label.
Evening
- Added team-member assignment to the order edit page so admins can assign photographers and editors directly while editing the order.
- Added proper date and time pickers to the edit page (matching the new-order page's components).
- Made the cascading combined email send even if the invoice was already separately sent - the customer still gets one consolidated message.
- Wired listing media into the social publish dialog so the photos a customer wants to share are pre-loaded into the post.
Day 106 - Monday, April 27, 2026
A short Monday focused on invoice and reminder hygiene, plus a quiet email-deliverability upgrade.
Evening
- Locked invoice deletion and voiding once a payment has been received - paid invoices stay immutable for accounting integrity.
- Wrote audit scripts to verify reminder timing and team-member email coverage.
- Tightened invoice reminders to fire after the invoice's due date (not its creation date), and never before the due date arrives.
- Confirmed the email service provider's compliance certifications.
- Froze pricing values when an order is opened for editing so legacy modifiers stay honored even if the catalog has changed.
- Made sure order notes flow into the Google Calendar event description.
- Switched the email sending domain to a dedicated subdomain for better deliverability and clean separation from human-sent platform mail.
Day 107 - Tuesday, April 28, 2026
The day multi-session orders got rewritten on a proper foundation.
The first version of multi-session orders shipped back on Day 93 (Feb 14) but rode on top of the existing single-session schema. That was always a stopgap. Today the team replaced it with a real job_sessions table and rewired every part of the order flow to use it.
Afternoon
- Walked the dashboard's Today's Schedule widget over from raw job records to the calendar-events table, so the dashboard reflects the same source of truth as the calendar itself.
Evening
- Ran a 9-table audit across the order and calendar systems, locked down access rules across all of them, and dropped six columns on the jobs table that no code path was using anymore.
- Shipped multi-session orders v2 across six phases in a single evening:
- Phase 1: new
job_sessionstable that holds each session as its own row. - Phase 2: helpers and validation schemas for working with sessions.
- Phase 3: every order-creation path now writes session rows into the new table.
- Phase 4: the order detail page renders sessions from the new table.
- Phase 5: an inline Sessions UI on the order edit page so admins can add, remove, and reorder sessions in place.
- Phase 6: bulk-confirm flow links each calendar event back to its specific session.
- Phase 1: new
- Caught and corrected an event-type regression introduced during the rewrite.
Day 108 - Wednesday, April 29, 2026
A long day focused on locking down the auth and role boundaries, plus a brand-new Action Items widget for the superadmin team.
Morning
- Documented the multi-session schema guide for future reference.
- Added a database trigger that prevents anyone from changing their own role - the platform now enforces at the database level that a user cannot promote themselves to a higher tier.
- Built a unified user-relationships helper so the platform can answer "what assets does this person have access to?" in one place - the foundation for asset-centric access checks across the codebase.
- Closed four access boundary items: admin invitations now handle existing users cleanly, and three role-preservation paths around signup completion and OAuth callbacks were tightened so a user's role can't be silently changed during account flows.
Afternoon
- Removed unnecessary date/time picker restrictions on admin-side scheduling - admins can now pick any time, since they're scheduling on behalf of their business.
- Surfaced open support tickets at the top of the superadmin dashboard so the platform team sees what needs attention immediately on login.
- Suppressed per-session calendar emails on multi-session order creation (the consolidated order email already covers everything).
- Made the Send button visible when editing a draft invoice (was only appearing on freshly-created drafts).
- Persisted line-item comments through the invoice update API so notes survive across edits.
Evening
- Built the superadmin Action Items widget: a personal task tracker for the platform team, with a backing table, type system with strict validation, server actions for create/update/toggle/delete, and the widget itself wired into the superadmin dashboard.
- Locked superadmin email exclusivity at every layer:
- Database-level enforcement that no admin can share an email with a superadmin account.
- An application helper that recognizes superadmin emails everywhere they need to be recognized.
- Guards added to every signup path, every admin-invite path, and every Tier 2 endpoint so a fresh superadmin email can't accidentally be claimed by a regular admin.
Day 109 - Thursday, April 30, 2026
A long, multi-thread day. The CRM rebuild started, AI editing got a custom-prompt mode, a new floor-plan integration shipped, and an interactive 3D landing page went live.
Morning
- Added a signup blocklist and cleaned up fake test admin accounts.
- Built an admin display-name fallback chain so accounts without a business name still render cleanly.
- Started role-collapse Phase A: a schema change that moves away from rigid role columns toward a flexible
admin_featuresmodel where each business turns on the parts of the platform they actually use. - Backfilled
admin_featuresfor all existing admins so the new model lights up immediately.
Afternoon
- Wired the admin's timezone into the dashboard, calendar surfaces, jobs, messages, and public order pages - every time a business sees a date or time, it now reflects their declared timezone.
- Built the Home3DS floor-plan import integration end-to-end: connect/disconnect flow, settings card, task list, import modal directly inside the floor plan gallery, error handling for vendor authentication, with the import UX baked into the existing upload modal as a tab.
- Launched the interactive 3D universe landing page at
/about/captains.html- an exploratory marketing piece showing the platform as a connected universe of independent operators.
Evening
- Wired the admin sidebar to read from
admin_features, and built the Settings → Features panel where business owners can turn modules on and off. - Locked email immutability on the profiles table: a user's email becomes their permanent identity once set.
- Installed an architectural guardrail (a lint rule plus a shared admin-timezone context) so future code can't accidentally render dates in the server's timezone again.
- Launched the new AI editing pipeline:
- Phase 1: the new AI provider, the background worker, and the type system.
- Phase 2: a custom-prompt input and automatic capability discovery (the platform learns what each AI model can do).
- Custom prompts now flow through the AI Edit modal's quick actions.
- No-credit admins can use a free AI flow without hitting the credit gate.
- Started CRM Phase 0 (the data-model foundation for the CRM rebuild): updated client deletion policy to set-null instead of cascade, and added a per-admin uniqueness rule on segment codes.
- Expanded the signup form with optional profile fields so new businesses can pre-fill more of their setup.
Day 110 - Friday, May 1, 2026
Goal for the month - Keep testing what April's work made possible and broaden the platform with new capability: ship a new front page that lands the platform's core principle (one person = one email = one entrepreneur), launch the affiliate program, push the CRM deeper, and keep tightening security.
The day the CRM rebuild went from zero to functional in production.
CRM Phase 0 (the data model) and Phase 1 (form capture and fan-out) both shipped today, plus the start of Phase 2 (the new UI). Trial billing, onboarding expansion, and floor-plan AI editing all landed alongside.
Pre-dawn
- Polished the AI editing flow: instruction forwarding, no-refund-on-free-fails, source URL resolution for archived images, single-step downloads.
- Shipped CRM Phase 0 phases P4 through P22 - a marathon migration sequence that builds the complete data model: lifecycle stage enum, 21 new columns and 6 indexes on the contacts table, canonicalized email and citext columns, a new pipelines table with stages and a denormalization trigger, deals with deal-clients many-to-many, an activities table with monotonic last-contact triggers, tasks with next-followup tracking, notes, attachments, forms and form submissions, an audit log, a deal-id link on jobs, and seeded default pipelines.
- Built the onboarding extraction-review step so signups can confirm what the AI extracted from their website before the account is created.
Morning
- Added the "Improve my Digital Footprint" signup opt-in (flag-only for now).
- Added the feature questionnaire as step 2 of onboarding so businesses pick the modules they want during setup.
- Dropped the role-selection step from signup entirely - role is now derived from the relationships and feature toggles instead.
- Shipped CRM Phase 1: the public-facing capture pipeline. Form submissions flow through a security-defined RPC with idempotency, into the new CRM data model. Fan-out to email, CSV import infrastructure, an unsubscribe page, a GDPR data-export endpoint, and tests.
- Refactored four legacy contact endpoints to use the new submitForm pipeline, and replaced reads of the legacy submissions table.
Afternoon
- More AI editing polish: image modality on the API request, approve-flow that adds a new image rather than replacing the original, auto-import edits into the gallery, unique-checksum enforcement, and floor-plan support added to the editing schema.
- Built Stripe trial checkout (S6.1): card capture during active trials, with a webhook that converts the trial when the timer ends and a cron that sweeps stale trials.
- Started CRM Phase 2 - the new UI: section RPCs with GDPR erasure built in, a sidebar rename, lifecycle tabs, and a leads inbox.
Day 111 - Saturday, May 2, 2026
CRM Phase 2 wrapped up and the AI edit experience got a clean redesign.
Morning
- Renamed the AI editing "preset" concept: photos get preset tiles, floor plans get free-form input.
- Per-tab lifecycle descriptions on the new contact view so admins know what each lifecycle stage means.
- Collapsed the AI edit flow into a single screen: every preset becomes an editable prompt, every screen leads to the same Generate action.
- Reframed the AI edit prompt screen so "Generate" is unmistakably the next step.
- Rewrote CRM copy in a friendlier voice and reorganized it into a 4-wide surface grid for clearer scanning.
- Polished the AI edit modal layout: 4 tiles per row, trimmed help text.
Afternoon - CRM Phase 2 ships
- Slice 2b: inline-edit sections for contact identity, contact info, consent, tags, and category.
- Slice 2b sub-3: the Activity Timeline embedded directly on the contact detail page.
- Slice 2c: a deals kanban with drag-to-move between stages.
- Slice 2d: CSV import UI plus forms management.
- Slice 2e: compliance review, categories, and GDPR erasure surfaced in the UI.
- Slice 2f: manual fan-out re-trigger from the contact detail.
- Slice 2g: AgentDetails section, inline Notes, mark-not-spam controls, and keyset pagination on the timeline.
- Trial UX polish (S6.3): persistent dismiss state, sub-day countdown when the trial is almost over, a silent-path daily cron for follow-through.
- Security hardening (S1.2): a database trigger that locks
plan_tierandtrial_ends_atagainst client-level edits - pricing tier changes can only flow through the billing layer.
Evening
- Renamed "Client" to "Contact" throughout the CRM tabs and added live tab-count badges so admins can see at a glance how many leads are in each stage.
- Re-scoped a security item that was waiting on a Supabase hook that doesn't exist (rerouting to a different mechanism).
- Cleaned up an unreachable code branch in the OAuth callback.
- Anchored invoice reminders on the date the invoice was actually sent rather than its due date - a more accurate trigger for follow-up timing.
Day 112 - Sunday, May 3, 2026
A long Sunday with one major launch: the CRM Insights / Review Digest. Plus a stack of order-form polish, weather-forecast features for shoot dates, legal compliance updates, and three new build-time guardrails.
Morning
- Polished the new-order form: blocked accidental Enter-submits, added a notify-client modal on the edit page, surfaced "Add Event" errors on the calendar, and allowed multi-day blocked time.
- Tightened the order form behavior with input-method support, defensive parsing, and dismissable toast notifications.
- Added admin-wide frequent-services chips that surface a business's most-ordered services as quick-add buttons in the order form.
- Built per-admin opt-in weather forecasts on order pages: an admin can turn on weather lookups, and from then on every order page shows a forecast for the shoot date and address.
- Built time-on-site at booking with an admin override so the duration estimate respects each business's defaults.
Afternoon
- Updated the Terms of Service and Privacy Policy to align with A2P 10DLC (the carrier registration that governs business SMS).
- Built explicit no-forecast UI states and a reason whitelist for the weather card.
- Wrote up a 3-features correction plan with dual-review notes from independent AI reviewers.
Evening
- Launched CRM Phase 5 - the Insights / Review Digest. Each business now gets a periodic email that summarizes their CRM activity: lifecycle progression, deals moved through pipeline stages, contact engagement scores, things that need their attention. Opt-in by default, themed to match the rest of the platform's settings.
- Cleaned up the email layer: dropped the "Powered by" footer from all transactional emails (a more polished sender identity).
- Built per-session weather forecasts for multi-session orders, plus a new-order weather preview and improved geocoding accuracy for city-level lookups.
- Capped the form-submitted fan-out worker at 5 concurrent jobs to keep the queue smooth.
- Retired a defensive CRM notification sweep cron now that the proper event-driven path is reliable.
- Refreshed the email deliverability audit with current verified state.
- Added a dead-code detector to the project tooling.
- Added three new build-time guardrails: migration drift (catches schema files that diverged from the database), a service-role allowlist (prevents accidental privilege escalation in code), and integration-map drift (catches code that drifts from the documented integration map).
- Routed the insights-digest claim through a security-defined RPC for proper boundary enforcement.
Day 113 - Monday, May 4, 2026
The day the build journal you're reading right now went live. Plus three sub-phases of CRM Insights and a digest-email polish pass.
Morning
- Built the build journal at
/journey- the page documenting LOTULIS's day-by-day construction. Public-but-unindexed, sourced from a single markdown file, rendered with a growing-tree timeline that maps each day's themes to colored lanes branching outward as the project grew. (This page is itself one of the entries.) - Shipped CRM Insights Phase 5.1 - confidence column. Each insight now declares whether its conclusion comes from a clear-threshold rule (high confidence) or a heuristic that uses proxy data (medium). Surfaces in the UI as a small badge so business owners know how much weight to give each suggestion.
- Shipped CRM Insights Phase 5.2 - five new rules. Five additional insight types added to the digest, each tuned to a specific business pattern: scheduling load, delivery speed, pipeline conversion, and others.
Afternoon
- Shipped CRM Insights Phase 5.5 - AI prose layer. The digest now opens with an AI-generated narrative paragraph synthesizing the period's metrics into one coherent story, before the structured stat box and suggestions list. Tier-gated to the paid plans; Free-tier digests keep the bullet-list format.
- Updated the segment-recompute logic to read from the canonical membership-rules column instead of a legacy field - a quiet correctness win that fixed a long-running edge case where some segments matched everyone in a business's contacts.
- Polished the digest email itself: the greeting now uses the owner's first name, the subject line uses the business name, and the body got trimmed for cleaner scanning.
Day 114 - Tuesday, May 5, 2026
A long Tuesday of polish + the launch of one-click insight actions, plus a meaningful expansion of the project's North Star.
Morning
- Shipped CRM Insights Phase 5.6 - action surfaces. Each insight in the digest now carries a one-click action: send the contact a follow-up email, schedule a call, mark not-spam, move to a different stage. The digest stops being read-only; it becomes the place work happens.
- Stopped double-syncing new orders to Google Calendar (the consolidated path now does the right thing).
- Tightened the bulk PATCH cancellation email so it shows the order's real prior status.
Afternoon
- Routed five direct email-send sites through the central suppression-checked wrapper so unsubscribed addresses never receive accidental sends.
- Notify the assigned editor when an admin requests batch revisions on a session.
- Fanned out job emails and bell notifications to all clients on a job, not just the primary one.
- Added BCC count logging with masked addresses for invoice sends.
- Replaced an optimistic calendar availability banner with an honest "couldn't verify" message when the network check times out.
- Auto-fetch delivery metadata at the call site when the caller doesn't provide it.
- Closed a cross-tenant input boundary: the public order submit now rejects a
client_idthat doesn't belong to the calling business. - Sync the invoice's snapshot of the client when the client record is edited.
- Locked down a database constraint on the invoice
additional_recipientsarray so only valid emails can land there.
Evening
- Ran a multi-table audit batch and tightened access rules on pending admin signups, photographer availability, and photographer blackout dates.
- Refactored the CRM cron to event-driven segment recompute (Phase B): segments now recalculate the moment a relevant change happens, rather than waiting for a periodic sweep.
- First overhaul of this journal page - added a timeline rail, theme styling, and a "Growth" section. (A second overhaul on Day 115 replaced this with the growing-tree visualization you're scrolling now.)
- Added Stage 6 to the platform's North Star. The vision doc now extends out to the endgame: the platform as a global mesh of independent entrepreneurs - operators finding and working with each other, regardless of geography. The journey is now framed as a path toward that.
Day 115 - Wednesday, May 6, 2026
The day CRM engagement scoring and automation rules went live, and the journal page got the visual you're looking at right now.
Morning
- Made AI editing await its provider call so it survives serverless function termination cleanly.
- Polished
platform_config: documentation, an automatic timestamp trigger, and a quick design review.
Afternoon
- Big database audit batch - tightened access rules and added documentation across
platform_emails,platform_invoices,price_lookups,profiles,rate_limits, andretention_events. - Stopped duplicate "listing delivered" emails to team members (when a single listing has multiple addressees, each only gets one email).
Evening
- Changed the platform's marketing tagline from "all-in-one for real estate photography" to "Many Industries, one Platform" - completing the framing shift that started with the Day 45 listings → galleries pivot.
- Replaced the journal page's per-day timeline with a growing-tree visualization (the one you're scrolling). Lines split when new themes first appear, merge back occasionally for visual variety, and bend inward when a sibling lane terminates so the tree stays packed.
- Added a script to clean up orphaned Google Calendar events that lost their parent order.
- Shipped CRM Phase 6 - engagement scoring + automation rules. Each contact now carries an engagement score that updates as activities flow in, and admins can build automation rules ("when a contact crosses score 80, add them to the High-Engagement segment", "when a deal stalls in Discovery for 14 days, send the owner a nudge").
- Dropped the lifecycle outbox table - events now emit directly at their three call sites instead of through a defensive intermediate buffer.
- Collapsed the score-recompute job to a single daily cron.
Day 116 - Thursday, May 7, 2026
A big-feature day. Custom domains for property listings shipped end-to-end, the CRM gained an admin form builder, and the substrate for a tenant website builder went in.
Morning
- Shipped CRM Phase 7 - admin form builder. Business owners can now build custom contact forms directly inside the CRM, with each form getting its own per-admin slug route on the public side. No code, no embeds - just configure fields and share the link.
Afternoon
- Started Listing Domains - a feature where each listing can be hosted on its own custom domain (think
123-main-street.cominstead of a long platform URL).- P0: data model + roll-out plan.
- P1: integrations with the domain registrar and the hosting layer.
- P2: background provisioning pipeline + a superadmin tool for QA.
Evening
- P3: customer checkout flow built on Stripe - search a domain, see the price, pay, and the platform handles registration and DNS automatically. Wave 2 added the UI, the email templates that fire at each step (purchase confirmation, provisioning complete, DNS verified), and the background workers that send them.
- P4: the platform's request layer now serves the right listing when someone visits a custom domain, complete with the full property page experience on that domain.
- Started the Web Builder (Phase 0): the substrate for entire tenant websites, plus the first pre-built theme ("Classic Kitchen & Bath"). Each business will eventually be able to spin up a full marketing site on their own subdomain or custom domain.
- A long polish stretch on Listing Domains: TLD dropdown next to the input, inline format errors as you type (no waiting for blur), tighter email regex, distinguishing pending-payment from active provisioning, surfacing underlying provider errors, and recovering from stale Stripe customer references during test/live mode swaps.
Day 117 - Friday, May 8, 2026
A focused day with two real new features and a stack of domain polish.
Morning
- Shipped the Affiliate Program (Phase 2 v5). First public version of the affiliate / referral program - the database backbone, the application flow, and the initial UI for the Sales Partner role to track their referrals.
- Renumbered the listing-domains rollout to make room for Phase 5 and added a health-check endpoint for the domain provisioning pipeline.
Afternoon
- Built "Bring Your Own Domain" (BYO) for listings. Business owners who already own a domain can now connect it to a listing without buying a new one through the platform - a verification flow + DNS instructions + automatic certificate provisioning.
- Closed a BYO race condition where two simultaneous connect attempts could leave the hosting layer in an inconsistent state.
- Surfaced Get/Connect entry points consistently even when a previous attempt was in a failed state.
Evening
- Built a public
/sms-termspage for A2P 10DLC review - the SMS carrier compliance flow requires a publicly accessible terms page that explains opt-in and message frequency. - Wired
/sms-termsthrough the auth middleware so it loads without a login. - Polished domain registration: recover gracefully from already-registered retry cases, trust the registrar's reported expiration date.
Day 118 - Saturday, May 9, 2026
A massive day. Web Builder onboarding started, the CRM gained a full outreach composer, affiliate commissions went live, and superadmin got a cross-admin network view.
Morning
- Dropped an unused legacy reviews table after a clean audit fork decision.
- Shipped Affiliates Phase 3 - commission ledger + signup attribution. Affiliate referrals are now tracked end-to-end: when a referred user signs up, the attribution is recorded; when they become a paying customer, a commission accrues on the ledger.
- Saved-filters polish: finished auto-apply of defaults, split the galleries umbrella across the saved-filter system, and surfaced a quiet bug where segment + search filters were silently neutered by a hidden default-stage filter.
- Right-anchored the saved-filter dropdown so it doesn't clip on page edges.
Afternoon
- Built CRM Outreach - a full outreach composer inside the CRM: templates, tracking (delivery, open, click, reply), and per-contact history. Admins can now run outbound campaigns without leaving the platform.
- Added shared calendar event description and title builders so every place that creates an event uses the same format. Wired live-fetch enrichment into the Google Calendar sync.
- Added a day-before weather refresh background job that updates a shoot's forecast 24 hours before showtime.
Evening
- Built CRM badge + chip primitives (LifecycleStage, Score, Segment, Status) so the new CRM views speak a consistent visual language.
- Extracted the
emitClientChangedhelper to a shared module so the same event fires identically from every call site. - Shipped the first cut of the Superadmin CRM (Phase 1): a cross-admin network aggregation route + the database functions that power it. The platform team can now see the full contact graph across all businesses in one view.
- Started the Web Builder onboarding wizard (Phase 5.1) - the guided flow that captures a business's brief (their offerings, voice, target audience, sample content) and a schema for storing it.
- Web Builder Phase 5.2 - AI-powered brief synthesis: an AI takes the raw brief input and produces a structured site plan, with content moderation on the way in.
Day 119 - Sunday, May 10, 2026
The day the Web Builder shipped end-to-end and the platform gained tenant subdomains.
Morning
- Polished the listings page: full inline Order Connection tab, Edit Order in the header, dropped a redundant invoice generation block.
- Web Builder Phase 5.3 - data model, a 12-block kit (hero, services, gallery, testimonials, contact, etc.), and the rendering substrate.
- Renamed
middlewaretoproxyto match the framework's new conventions, dropped a conflicting cache-control header.
Afternoon
- Closed three security audit batches (
security_action_audit,security_alert_actions,security_alerts) - re-scoped access rules, added foreign-key cascades, added comments. - Web Builder Phase 5.4 - the AI site generator pipeline. The brief from the wizard now feeds an AI generator that produces a complete first-cut site (text + layout) which lands in a draft state for the business owner to review.
Evening
- Web Builder Phase 5.5 - edit UI + publish flow. Business owners can edit any text, swap any image, and click Publish.
- Web Builder Phase 5.6 - public hosting at
<slug>.lotulis.com. Every business gets a free subdomain for their site, immediately. (Day 116's listing-domains is the per-listing version; this is the whole-site version.) - Closed two more security audits (
security_scans,security_settings) and the service categories drift bundle. - Added a Website entry to the admin sidebar pointing at the new builder.
Day 120 - Monday, May 11, 2026
Short Monday - two polish items on the Features tab.
- Fixed the Features tab loading state.
- Split a type import that was tripping up the build tool's tree-shaking.
Day 121 - Tuesday, May 12, 2026
Affiliate program split into two programs, the cron stack got an event-driven rewrite, and a long-missing audit log table finally landed.
Afternoon
- Split the affiliate program into two products: a credits-based program (referrer gets credits) and a cash-based program (referrer gets paid). New schema + an auto-create trigger that picks the right program for each new affiliate.
- Built the GDPR Subject Access Request (SAR) endpoint - customers can now request their data, the platform produces a complete export, and a janitor cron retires old request tokens.
- Affiliate Phase B - credits routing live in the accrual RPC + UI surfaces showing credits dashboard.
- Added self-service "leave and rejoin" so an affiliate can pause their participation without losing history.
Evening
- Wired credit redemption to fire on Stripe invoice creation so referred customer credits get applied automatically.
- Affiliate Phase D - clawback + redemption reversal: if a referred customer churns or refunds, the affiliate's commission is reversed cleanly.
- Created an
audit_logstable - finally closes a four-month gap where some events were emitting without anywhere durable to land. Every privileged action now has a permanent record. - Converted four background-job crons (BYO domain verification, the listing-domain pending-purchase sweep, CopyPro batch scanning, Web Builder generation watchdog) from polling crons to event-driven workflows that fire only when there's actual work to do.
- Closed a long retired-cron item (
crm-recompute-segments- replaced last week by the event-driven recompute). - Affiliate Phase C - full application + renewal-ladder + revoke flow, with email notifications wired for every state change (apply / approve / revoke / renew).
Day 122 - Wednesday, May 13, 2026
The single biggest CRM-and-affiliates shipping day of the project. Three new CRM modules, an Estimates feature shipped end-to-end, Stripe Connect payouts for affiliates, and a massive table-audit batch.
Morning
- Built admin self-service "Download my data" - every admin can now export their platform data on request, satisfying GDPR Art. 15 without a manual process.
- Retired the SAR token-based substrate after a re-scope (the SAR endpoint shipped Tuesday handles the same need more cleanly).
- Affiliate Phase 4 - Stripe Connect onboarding for Sales Partner payouts: affiliates who chose the cash program connect their bank account through Stripe Connect, and the platform sends them their commission automatically.
- W-9 PDF download for superadmins (US tax compliance for affiliate payouts).
- Stripe Connect payout saga + webhooks - the full transfer pipeline, with idempotency and webhook verification.
- Added a 90-day auto-delete cron for unapproved Sales Partner applications.
Mid-day
- Shipped Estimates as a new module - every phase in one day:
- Phase 1: admin substrate + draft/sent flow.
- Phase 1.5: form mirrors the new-invoice form so admins don't relearn anything.
- Phase 2: public estimate page + send-email.
- Phase 3a: accept / reject / convert-to-order.
- Phase 3b: tabs + client section + CRM lifecycle hooks (an accepted estimate moves the contact through the right pipeline stages automatically).
- Phase 4: expire-stale-estimates background job.
- Estimates now duplicate using the current admin branding (not the source estimate's stale snapshot).
Afternoon - CRM expansion
- CRM Phase 7 - drag-and-drop field reorder for the form builder.
- CRM Phase 8 - Reports + Forecasting dashboard: pipeline value by stage, conversion rates, expected revenue, period-over-period trends.
- CRM Phase 9 - Companies UI: contacts can now belong to a company, and the Companies tab shows the org-level view.
- CRM Phase 11 - two-way Google Calendar sync: changes in either direction flow through cleanly with loop prevention (a thoughtful end to a feature that's been polished iteratively for months).
Evening
- Closed wide-open access on three listing-related views (listing_order_notes, listing_page_views, listing_tour_views) - scoped to authenticated admins only.
- Auto-revoke Sales Partner status when Stripe terminally rejects the onboarding application.
- An automated SMS bypass flag for when the SMS provider's status webhooks are misbehaving.
- White-label master plan v2 published (corrects a tier reference that was wrong in v1).
- Audit walk continued through more than a dozen tables today - the systematic database audit started on Day 89 is now most of the way through the platform.
Day 123 - Thursday, May 14, 2026
The biggest white-label day on the calendar. The morning was polish; the afternoon and evening collapsed five separate white-label phases into a single unified substrate, stood up commercial galleries as their own sibling to listings, and shipped a public download gate for paid commercial work. Plus a quiet but critical fix to a database bug that had been silently blocking every new admin signup for twelve days.
Morning
- Reverted the white-label auto-poll experiment from Phase 3 - verify-on-click turned out to be the right design (less noise, cleaner UX).
- Renamed the Website module to "WeBuild" and moved it into the PREMIUM sidebar section, signaling that the site-builder is a paid-tier feature.
- White-label Phase 4 M2 - cold-start throttle for new tenant sites, webhook health counters that record DNS verification reliability, and a clean deactivation pathway.
- White-label Phase 4 M2b - tier-based daily email caps so each tier gets a hard outbound-email quota, with messages dropped (and the admin notified) once the cap is hit.
- Deleted a stale
CollapsibleSidebar.tsxthat nothing references anymore. - Built drag-to-reorder for the per-admin sidebar in Settings → Features: admins can now arrange their navigation in the order that fits their workflow.
- Made the listings deliver flow stop silently aborting when a draft order is selected - the cascade now surfaces the situation clearly and lets the admin decide.
Mid-day
- Cleaned up the branding form - dropped a dead "Email Sender Name" field that didn't wire anywhere, then rebuilt the sender preview so it tells the truth about what actually gets sent.
- Unified two separate "domain" cards into one "Your domain" section. Admins were getting confused which knob to turn for what; now there's one card with both subdomain and apex side by side.
Afternoon - the great white-label collapse
Five phases of branded-domain plumbing shipped back-to-back as a unified substrate. The end result: one admin row holds both their *.sites.lotulis.com subdomain and (optionally) their custom apex domain, with a single attach/verify flow.
- Phase A - unified substrate. Subdomain and apex now live on the
adminstable itself instead of in a side table. Added functional unique index + extended reserved-word list so admins can't grab "www" or "admin" as their handle. - Phase B - unified attach UI + API + provisioner. One screen, one endpoint, one provisioner that handles both flavors.
- Phase C - buy-a-domain inline. Carlos can now offer a "buy your domain" path inline from the white-label setup screen.
- Phase D - wired the rest of the platform onto v3.
- D-Email: 5 transactional send paths rewritten to use the new model.
- D-Web:
proxy.tsnow routes the WL subdomain to public paths. - D-URLs: every client-facing link (download pages, share links, magic links) now renders on the admin's WL host instead of the platform host.
- D-Web-Apex: opt-in serving of the admin's portfolio at their bare apex (e.g.,
clientname.cominstead ofwww.clientname.com). - D-Images: the admin's WL host serves their image URLs too, so embedded photos in their site render under their brand.
- Phase E - cleanup. Dropped legacy M1/Phase 3 columns now that nothing reads them.
- Added the apex opt-in UI + verify-path extension on top of all that.
- Added an Upgrade to Pro button on the Starter-tier banner so admins on the free path can convert without leaving the white-label screen.
Late afternoon - commercial galleries: stand-up + tear-down + stand-up
A long iterative session building commercial photography galleries (paid-license photo sales) as a sibling product to listings. The plan was to mirror the listings tree and prune what doesn't apply to commercial work. The pruning went wrong twice and got reverted both times before settling on the right shape.
- M0 - image licensing schema substrate (new tables for license terms, watermark policy, expiry).
- M1a - first stab at list/create/detail screens. Wrong mirror source. Reverted.
- M1-mirror - re-mirrored from the correct listings tree.
- M1-prune-1 - removed the sessions subtree (commercial galleries don't have shoot sessions). Got reverted later in the evening because sessions actually do matter for commercial.
- M1-prune-2 - removed floor-plan UI. Reverted - turned out commercial archs needed floor plans too.
- M1-prune-3 - removed 3D tour UI. Reverted for the same reason.
- Wire-up - list / new / detail working end-to-end against the gallery data model.
- Sessions subtree restored by reverting M1-prune-1, closing the loop on the three prune reverts.
- Full media surface + invoice + Stripe webhook → Inngest. Commercial galleries now have a complete media surface (photos / videos / docs), generate invoices, and run an Inngest job off the Stripe webhook on payment.
Evening
- The 12-day silent admin-INSERT bug. A seed function (
seed_legacy_crm_forms) hadON CONFLICT (slug)where the actual unique index is(admin_id, slug). Postgres returned42P10at plan time, which silently blocked every new admin INSERT for 12 days (2026-05-03 → today). The bug surfaced the moment Carlos tried to invite Robert Buntin onto the platform. Fixed with a one-concern repair migration. - Galleries umbrella wired up.
/admin/galleriesnow shows both listings and commercial galleries side-by-side as tabs; deep-links via?tab=honored so external links land on the right view. - Resend webhook crash fix - Resend started sending event
tagsas an object map instead of an array, which was crashing the delivered-event handler. Tolerant parser merged. - Public
/license/[slug]gate + download proxy - the public-facing commercial gallery flow: visitor lands, accepts license terms, signs (next session ships the watermark + NDA polish), then gets a one-time signed download URL. - Superadmin Inbox bulk-select + bulk-delete - small ops polish; the support inbox was hard to clean up without it.
- DNS-record parsing fix for white-label. Resend was returning 4 DNS records in the verify payload; the parser was only emitting some of them. Admins were seeing "DNS not configured" because one record was missing from the instructions card.
Day 124 - Friday, May 15, 2026
A long mixed session anchored on three macro threads: the first bespoke-client onboarding (Robert Buntin / CKB Design Studio), the standing up of a continuous security-assessment pipeline (AIDA + 6 custom MCP tools), and the first three live security findings remediated against production. Plus several small infrastructure fixes and an enforceable consent system for the Sales Partner Program.
Pre-dawn
- Cookie
Path=/fix on the commercial galleries license cookie so the download endpoint actually receives the session token.
Morning - Robert Buntin bespoke tenant (first hand-designed client)
The block-kit web builder is the right MVP for self-serve. But Robert had a hand-designed Astro mockup that didn't fit the block model, so a second tenant-hosting path got built so he could be hosted at fidelity.
- New
admins.bespoke_site_pathcolumn +bespoke_site_configsSECDEF view so anon traffic can resolve which slug serves which bespoke directory. proxy.tsresolves bespoke hosts before the block-renderer path; rewrites to/tenants/<slug>/....next.config.mjsgot cache-header rules for/tenants/:slug/*- 1-year immutable on_astro/*, 1d/7d onimages/*, 60s/600s on HTML.- Path-traversal hardened (rejects
..,\,\0). - A platform-host
/tenants/*404 guard so the directory isn't reachable fromlotulis.com. - Robert's admin row created. His built Astro
dist/(17 MB, 9 photos, 5 HTML pages, CSS) committed atpublic/tenants/ckb-design-studio/. - Apex
ckbdesignstudio.comattached to Vercel via a one-off script. DNS records pending Robert's registrar setup. - One-off script
scripts/dev/attach-apex-robert.tswritten and committed so future bespoke onboardings have a template to follow. - Mirrored the listings download UX into
/license/[slug]so commercial-gallery customers get the same polished experience as listing visitors.
Mid-morning - infrastructure
- Pinned
isomorphic-dompurifyto^2.26-jsdom@29was being pulled into the chain and breaking prod Node. - Raised
maxDurationto 300s on the calendar-sync Inngest function so bulk re-syncs don't time out. - White-label per-record verification status + a friendly "up to 24 hours" banner so admins don't panic when DNS takes time to propagate.
- Web-builder image path fix - tenant image requests were bypassing middleware due to a matcher exclusion; one-line fix.
- Resolved directory-shaped paths to Astro's
index.htmlso bare-directory URLs (e.g.,/about/) render the right page. post_payment_urlon invoices - when a paid invoice has a delivery link, share that link with the customer right on the receipt so they can download immediately.
Afternoon - AIDA pentest integration
A long architectural session standing up a continuous security-assessment pipeline. Cloned the open-source AIDA project outside the LOTULIS repo (AGPL boundary), forked to a local-only emarcott-custom branch, and built 6 custom MCP tools layered on top of AIDA's built-in toolkit. These tools target AI-built-code anti-patterns that off-the-shelf scanners don't detect.
The 6 custom tools (in ~/Documents/Projects/AIDA/backend/mcp/modules/emarcott_checks.py):
check_emarcott_rls_column_leaks- surfaces RLS policies that leak sensitive columns to anon/authenticated.check_emarcott_secdef_search_path- checks for unsafesearch_pathsettings in SECURITY DEFINER functions.check_emarcott_onconflict_unique_match- exactly the bug shape that bit us yesterday withseed_legacy_crm_forms.check_emarcott_ai_prompt_injection_sites- flags places where user input flows into AI prompts.check_emarcott_cross_tenant_access- gated for local-only execution; partial v1 (JWT fixtures are v1.1).check_emarcott_webhook_idempotency- verifies webhook handlers are properly idempotent.
- First assessment ran - 15 findings produced. Triaged into 3 fix-immediately, 4 investigate, 8 dismiss-as-false-positive-or-intentional.
- Pentest plan v2 committed (Codex + Gemini reviewed) - ongoing posture, 5 phases, AI-anti-pattern audit as Phase 0.
AUTHORIZED-SELF-TESTING.mdcommitted - explicit authorization document naming Carlos + the AI session as test operators.
Late afternoon - first three security findings remediated against prod
Three findings sealed with live exploits verified pre-fix (curl with anon key returned real PII) and live verification post-fix (42501 permission denied):
- F-9/F-10 listing_websites.access_password (HIGH) - column-level REVOKE of
access_passwordfrom anon and authenticated. The cleartext password column was readable by anyone with the public API key. - F-1 admins anon full-table leak (CRITICAL) - the
adminstable was readable by anon, including cleartext onboarding tokens. Dropped 2 anon policies, REVOKE ALL from anon, createdadmin_public_chromeSECDEF view that exposes only the columns a public portfolio page actually needs, and rewiredresolve-website-by-slug.tsto query the view. - F-3 clients anon dead-code policy (HIGH) - dropped a dead-code anon policy on the
clientstable; REVOKE ALL from anon. No app changes needed (public portfolio render uses the service-role client).
Evening - Sales Partner Program legal scaffolding
The Sales Partner Program needed enforceable consent before launch.
- Sales Partner Program Terms page (v2) - full text including age requirement, CSAM/illegal-content prohibition, structured payout, attribution rules, IP, and dispute resolution. Apply-form link wired.
- Explicit 18+ checkbox on the apply form, with server-side enforcement (not just client validation).
affiliate_consentsaudit-trail table - enforceable proof of who signed which version of the agreement at which time.- PDF copy on every signed agreement - partner receives a PDF copy of their signed terms; we keep one too.
Late night
- Web-builder wizard copy edits on the welcome + voice-sample steps.
- AIDA custom-tools design doc v2 (Gemini-reviewed; Codex was API-down that round).
- CI unblock - extended the service-role allowlist + cleaned up a
middleware.ts→proxy.tsdrift that was breaking the pre-push hook.
Day 125 - Sunday, May 17, 2026
A focused security day: every one of the 3 open follow-ups from Friday's AIDA session got closed, the exec_sql RCE primitive was found and sealed (CRITICAL), and the commercial galleries surface got its first polish pass (Batch A).
Pre-dawn / Morning - sealing the rest of the AIDA findings
- CRITICAL: anon could mutate
adminsvia SECURITY DEFINER views. The 2026-05-15 SECDEF view fix sealed directadminsaccess for anon - but two derived SECURITY DEFINER views still had the wrongsecurity_invokersetting, so anon could write back through the view. Sealed. - HIGH: anon could wipe
auth_audit_log- anti-forensics primitive. Anon had table-level TRUNCATE on the audit table, meaning a compromised public endpoint could erase the forensic record of its own exploit. Sealed. (Underlying root cause is systemic; sweep filed as follow-up.) - Triage of the 4 deferred AIDA findings - - F-2 calendar_event_reminders_sent (MEDIUM, sealed) - sole policy was
USING(true)on authenticated.- F-4 email_outbox (DISMISS) - properly scoped via helper-fn / subquery.
- F-8 listing_contact_submissions (MEDIUM, sealed) - wide-open authenticated SELECT on a deprecated table. The Explore agent originally triaged this as a false-positive; live
pg_policiesquery proved it wasn't. Self-corrected per thefeedback_verify_db_statememory. - F-14 services.lookup_key (DISMISS) - unused (0 rows), non-sensitive.
- Also surfaced:
serviceshad a misnamedUSING(true)SELECT policy attached to the wrong table. Sealed in the same sweep.
Afternoon - brand + jobs
- Replaced product-name references across source code with
"LOTULIS"/"AI"(the AI-CLI tool name was being mentioned in copy). The pre-commit hook now blocks the literal product-name string from landing in any file. - Job session duration fix - calendar event end_time and
job_sessions.durationnow honorshoot_duration_minutes_override. Previously the override was being ignored. - Polish: highlight the Invoices nav tab when on
/admin/estimates; add a Back link on Create Estimate.
Late afternoon - sealed cross-tenant SELECT leaks
A second sweep across the schema found a handful of cross-tenant SELECT leaks (a different bug class than the AIDA findings). Sealed in one rollup migration. Plus a hotfix for the listing_websites admin UI that was relying on one of the now-revoked grants.
- BYO custom domain help page for the listings module - clear instructions for admins who already own a domain elsewhere and want to point it at their listing.
- Killed the modal auto-poll on the same screen (same lesson as Day 123's white-label revert: verify-on-click is the right UX).
Evening - exec_sql CRITICAL
- CRITICAL: sealed
exec_sqlRCE primitive. Apublic.exec_sql(text)function existed as a SECURITY DEFINER withEXECUTEgrants implicitly available to anon. Anyone with the public Supabase URL + anon key could run arbitrary SQL as thepostgressuperuser. Sealed by DROP FUNCTION, then types regenerated, then a forensics probe run againstauth_audit_logto confirm there was no historical exploit signature. - AIDA batch triage - SECDEF hygiene tightening across the schema + 4 false-positive dismissals.
Night - commercial galleries Batch A
The polish pass on Saturday's commercial-galleries skeleton.
- Watermark support for pro previews.
- Pro share link that bypasses watermark for vetted prospects.
- License Agreement (renamed from "NDA" - the correct legal framing). Per-admin default template + invoice pre-fill so admins don't have to retype boilerplate every time.
- Hotfix: Batch A admin APIs were returning 404 due to a
galleriesRLS gap; sealed. - Public page-view tracking rewired to use the service-role client (anon writes were 42501-spamming the logs).
- Sealed anon/authenticated grants on 13 service-role-only tables - the systemic sweep filed from Friday started its first pass.
Day 126 - Monday, May 18, 2026
Tier 1 security audit shipped, estimates module got the long-promised "attach PDF on send + auto-create order on accept" pieces, and a substantial port of the captains-home universe to React + Three.js landed as the new marketing/about page. Plus a documentation refresh of the role-collapse plan against shipped reality.
Pre-dawn
- Tier 1 audit shipped. IDOR + webhook + storage sweep. 1 real IDOR sealed, a dead webhook handler removed, a storage leak filed for separate remediation.
Morning - security imports + estimates
- AIDA reports imported into
docs/security/+ a remediation summary written. Centralizes evidence for future pentest sessions. - Clearer copy for the Pro preview links section on commercial galleries.
- Locked down
message-attachmentsbucket - 1 of 3 personal-photo buckets that were too open. Two more to go.
Mid-morning - estimates module ships its missing pieces
- Attach PDF on send. When an admin sends an estimate, the customer-facing email now has a PDF attachment of the estimate.
- Auto-create order on public accept. When a customer accepts an estimate on the public link, the platform now automatically materializes a draft order with the estimate's line items pre-filled, so the admin can convert without retyping.
- HOTFIX: the Tier 1 lockdown regressed client-portal photo display; restored the same hour.
- Added
/estimatesto the proxy public-routes allow-list (was 404'ing for anon). - Dropped two non-existent columns from the estimate-accept job insert that were causing the insert to fail.
Afternoon - captains universe ported to React + Three.js
The single-file captains.html prototype (the "universe of entrepreneurs" idea from the North Star) got ported into the marketing tree as a proper React route with Three.js. Lives at app/(marketing)/captains/ with the universe component under components/marketing/universe/. Same visual + connection-mesh idea, but now part of the real marketing stack instead of a one-off HTML file.
Evening - role-collapse audit + integration tolerance
- Role-collapse plan refreshed to v4 against shipped reality - Phase C.5 doc audit closed the canon/banner/archival gaps that had accumulated since v3.
- Integration robustness - Home3DS integration now tolerates null payload data; added a diagnose endpoint so future tour-provider drift is easier to inspect.
- Session handoff doc for 2026-05-15 finally written + the "captains worldwide" tagline landed in the marketing copy.
Day 127 - Tuesday, May 19, 2026
A design-and-direction day. Zero code shipped. What got built was a complete design for expanding LOTULIS into property management as additive features - not a parallel product, not a pivot. Every PM-shaped need was mapped against existing primitives (services / orders / listings / gallery / documents / asset_participants / sales_reps); only six things turned out to be genuinely new.
Morning - framing locked
The architectural framing was committed before any design work began:
- Same app, same admin model. Not a parallel product.
- Universal admin model - owner / tenant / vendor are relationships via
asset_participants, not new role types. (Theclientrole is being sunset on its own track.) admin_featurestoggles only for genuinely optional feature suites. Most PM functionality is configurable use of existing primitives, NOT new toggles.- Additive schema only. Every new column has a default that preserves existing photography behavior. No breaking changes. No data migrations.
- Trust accounting NOT in LOTULIS. Folds into the planned end-of-year QuickBooks integration.
- Photography flows must not regress. Defaults:
listings.purpose='for_sale',jobs.job_type='shoot',invoices.recurrence_rule=null,payment_transactions.purpose='payment'.
Afternoon - eight design docs into the Obsidian vault
All under ~/Documents/Projects-Notes/00-Home/Plans/PM/:
- 00 Overview - index of the design folder.
- 01 Recurring billing - recurring invoices/services + cron worker.
- 02 Rental application - public form + admin inbox. Chosen as the first build.
- 03 Tenant screening - TransUnion ResidentScreening integration.
- 04 Lease e-signing - DocuSign / HelloSign integration.
- 05 Listing syndication - Zillow Group + Apartments.com feeds.
- 06 Owner statement - monthly statement generator.
- 07 Reports - rent roll / vacancy / delinquency / expirations.
- 08 Schema extensions - every additive column + new table in one place, so the database delta is reviewable as one document.
- AUDIT.md - verification of every claim in the design docs against the actual codebase. Three corrections applied where the design had drifted from reality.
Evening - first build chosen
- Rental Application form chosen as the first PM build. Smallest blast radius: pure-additive schema (one enum column + two net-new tables), stays away from the money path, doesn't touch hot tables, mirrors the existing
crm_forms+submit_form()RPC pattern. - Deliberate deferrals for v1: application fee charging, TransUnion screening integration, auto-create lease draft on approve, FCRA adverse-action letter.
- Roughly one focused week of work scoped: schema + form + inbox.
- PM-handoff doc written so the next coding session can pick up cold.
Day 128 - Wednesday, May 20, 2026
84 commits, the heaviest day in the journal so far. Three feature suites running in parallel and crossing finish lines together: PM Rental Application v1 finishes and PM Phase 2 (recurring billing) starts, Products Gallery Phase 3 (cart + checkout) ships end-to-end in eight slices, and the R1 role-collapse pilot rolls across every list view in /admin.
Morning - PM Rental Application v1 finishes
- Form steps 3 + 4 with Turnstile + submit handler (Day 5b).
- Application emails - confirmation to applicant + new-app alert to admin (Day 6).
- Admin applications inbox list view (Day 7).
- Application detail + approve / decline + decision emails (Day 8). v1 shipped.
- Audit follow-ups across earlier days: stale Day 3 copy refreshed, Day 6 email link flipped.
Mid-morning - Products Gallery catches up to a real e-commerce surface
- Categories & Tags sub-tab - full CRUD UI.
- Real product catalog list view replaces the cloned-from-listings UI.
- Cleared ~1,100 LOC of confirmed-dead clone leftovers.
- Variation image picker. Auto-flip status to
sold_outwhen stock hits 0. Turnstile on public comment submission. JSON-LD availability reflects inventory state. Public product page SEO (metadata + JSON-LD). Catalog shows "From $X" when variant prices vary. - Phase 3 ships in eight back-to-back slices - 3a cart + orders schema → 3b cart cookie + add-to-cart on public product page → 3c
/cart/[adminSlug]page → 3d checkout + Stripe Checkout session → 3e order confirmation page + buyer + admin emails → 3f admin orders dashboard + restock-on-refund → 3g atomic inventory decrement + Stripe webhook → 3h restock-after-refund integration smoke test. - Phase 3 audit fix-ups: currency consistency, idempotent merge, refund atomicity, stuck-order cleanup, country list.
- Phase 3.5 starts the same day: 3.5a/b tax engine + shipping zones, 3.5c discount codes, 3.5d buyer-initiated return requests.
- Phase 3.5 plan v2 written (tax + shipping + discounts + returns folded into one doc).
Afternoon - R1 role-collapse pilots roll out
The unified-aggregation pattern (one query that pulls every admin-row the principal can see, plus their asset_participants edges) lands across the main list views:
/admin/invoices,/admin/jobs(Orders),/admin/estimates,/admin/galleries,/admin/listings,/admin/calendar, and the unified/admindashboard.- "+ New X" CTAs gated by
canWriteAnyacross all R1 sections. - Per-event Calendar matrix gating.
- R1 Listings detail pilot - gate destructive UI for customer-side viewers; hide owner-only listing detail sub-tabs.
- The page-level
canWriteAnygating was reverted as the wrong abstraction; per-event matrix gating stays. - Invoice detail page works for customer-side viewers.
- Job-details endpoint works for customer-side calendar viewers.
- Drop identity-only own-admin from the relationship index.
- Standalone gallery creation + upload pipeline work under multi-tenant RLS.
- Pro-preview renders R2 derivatives, not just
file_path.
Late afternoon - PM Phase 2 (recurring billing foundation) begins
- Day 1: leases + recurring billing schema.
- Day 2: payment-method-policy module.
- Day 3: admin "create lease after approve" flow.
- Day 4a: tenant
/onboard-rentroute shell + NACHA + Stripe Setup Intent. - Day 4b: Stripe Payment Element + confirm route + Subscription.
- Day 5: daily lease / Subscription reconciliation cron.
- Day 6a: Stripe webhook lease lifecycle (3 events).
- Day 6b: invoice / payment / dispute webhooks + tenant emails.
- Day 7: admin leases UI + dashboard widget + listing card.
Evening - onboarding + marketing + Stripe API path bump
- Setup gets inline logo upload + brand color picker.
- Explicit plan-choice step for every first-login user.
- "Improve my Digital Footprint" defaults to checked on signup.
- Unified breadcrumb across
/signup → /verify-email → /setup. - Help center gets an onboarding-prep checklist page + PDF, designed for sales reps to hand to prospects.
- Affiliate dashboard: "Share onboarding checklist" CTA.
- Marketing home page: Get Started CTA band, quantified pitch with cited numbers, founder-as-first-customer testimonial, the System block becomes a 3-step flow (cards moved to
/features). - Stripe webhooks adapted to the new 2024-10-28 path changes.
Night - commercial gallery change-sets A / B / C
Saturday's Pro Preview surface gets three change-sets:
- A - cover image + repair-stuck-jobs UI + redirect fix.
- B - Pro preview link v2 with downloads + PIN + license.
- C - public preview page gates + downloads.
- Plus select-and-batch-download on Pro Preview links.
Polish + housekeeping
- Editing-sessions cleanup cron now also deletes
editing_session_filesrows. - Reverted the null-prototype on
serializeRelationshipIndex(Next.js 16 RSC boundary bites again). - Two Playwright bugs from Day 4 PM sweep fixed.
- Admin new-listing form drops required address fields.
jsdom+isomorphic-dompurifymarked as server externals.- Help-center sidebar nav gets the Onboarding Checklist.
- PM module Phase 2 plan written.
- Security scanner: 17 false-positives suppressed.
- Phone field strips control chars on signup.
next+uuid+cf-workers honodeps bumped; transitive vulns cleared.
Day 129 - Thursday, May 21, 2026
The day AI Tour Video starts taking shape as a real product (per-clip Veo generation, a music library, drag-reorder, beat-aligned crossfades) and PM Phase 3 (short-term rental bookings) ships end-to-end in seven slices.
Morning - AI Tour Video Phase 1
- Per-clip Veo generation + B2 staging.
- Superadmin-curated music library.
- Drag-to-reorder + music selector + best-practice tips.
- Per-clip worker reliability + quality pass.
- Music curation guide + Incompetech import script.
Mid-day - PM Phase 3 (short-term rental bookings) ships end-to-end
- Day 1: STR bookings schema.
- Day 2: pricing engine + payment-method-policy update.
- Day 3: admin tri-state rental toggle + STR config UI.
- Day 4: public booking widget + live quote API.
- Day 5: guest checkout (magic-link + Payment Element).
- Day 6: booking webhook handlers + confirmation emails.
- Day 7: admin
/admin/bookings+ dashboard widget. - Plus a bespoke-domain middleware fix for Next.js 16 rewrites under
/tenants/*.
Afternoon - Products + Commercial Gallery polish
- Publish / Unpublish CTA on the product detail page.
- Export a product to Dropbox / Google Drive with a JSON manifest; bulk export from the catalog; re-import a manifest from either.
- Currency formatter centralized.
- Product Tracking tab hooked to real sales data.
- View tracking + analytics tab on the product detail page.
- Tracking tab honors admin timezone for date windows.
- Race-safe tax / category lookup + clearer partial-import handling.
- Listings clone leftovers dropped from product detail page.
- Listings toggleable, drag-reorder, star-as-default.
- Surface signed-in account on the catalog so an empty list is self-explanatory.
- Commercial-gallery Pro preview links: share modal, edit modal, inline PIN reveal.
- Forward PIN in share email + capture visitor emails.
- Products tab on
/admin/galleriesroutes to the catalog.
Evening - mesh + AI tour polish
- Mesh: SenderBadge + SenderFilter across the R1 list sections.
- AI Tour Video: optional crossfades between clips (foreshadowing tomorrow's beat alignment).
- Superadmin read-only impersonation gap closed (defence in depth).
Day 130 - Friday, May 22, 2026
61 commits. Three flagship suites in flight at once: PM Phase 4 ships across 10 days of contracts / compliance / lifecycle, PM Phase 5 (Maintenance & Work Orders) starts and reaches Day 5, and Products picks up digital downloads (Phase B) plus three polish phases (A, B, C).
Morning - R1 polish + galleries permission policy
- Receiving admin can pay an invoice from their own dashboard (R1 invoice pay button).
- Gallery permission policy: non-owners can upload / reorder / AI-edit; rental is a hard no.
- Belt-and-braces
admin_idscope on the jobs detail page. - Dead
/onboarding-wizard+ legacy archive dropped. GalleriesTabNav+ listings page wired todefaultTypeId.
PM Phase 4 - contracts, compliance, lifecycle (10 days, all today)
- Day 1: contracts + compliance + lifecycle schema.
- Day 2: server-side PDF generation for lease contracts.
- Day 3: lease-contract template editor (admin default + per-listing).
- Day 4: tenant signature flow + reordered onboarding.
- Day 5: booking terms editor + guest acceptance + refund calculator.
- Day 6: compliance disclosure module + booking acceptance.
- Day 7: lease / booking lifecycle reminders + admin notifications.
- Day 8:
/metenant + guest dashboard. - Day 9: lease renewal + Fair Housing disclosure.
- Day 10: audit + prod-schema verifier (36 / 36 passing) +
ContractDocumentreturn type fix.
PM Phase 5 - Maintenance & Work Orders begins
- Day 1: schema (applied to prod).
- Day 2: tenant
/me/maintenancesurface. - Day 3: admin queue + detail + photo attachments.
- Day 4: three Inngest email functions for maintenance.
- Day 5: audit, tests, prod verifier, trigger hardening.
Products - digital downloads + polish phases
- B.1: digital assets schema + signed-URL SECURITY DEFINER RPC.
- B.2: admin Digital Files upload UI.
- B.3: cart / inventory / shipping RPCs honor
product_type. - B.4: public + checkout UI honors
product_type. - B.5: digital download delivery + email + returns guardrail.
- Phase A: tracking + low-stock alerts + return window (+ Codex fix-ups).
- Phase B: order workflow polish (+ Codex fix-ups).
- Phase C: bulk actions + CSV export + header stats (+ Codex fix-ups).
RLS - cardinality landmines (the polymath bug class)
When a user is a participant on more than one admin's data, scalar subqueries in policies blow up with SQL error 21000. Two were found and sealed today.
clients-insert media policies failed on multi-tenant users.comments+clientspolicies had the same landmine.
AI Tour Video - beat-aware + cost-aware
- Optional crossfades between clips.
- aubio-based BPM detector for the tour music library.
- Beat-aligned clip duration per selected music track.
- Usage preview in the modal (daily limit + over-limit guard).
- 90-day retention + admin pin opt-out + one-click regenerate.
R1 / role-collapse - SenderBadge everywhere + asset_participants triggers
- SenderBadge in detail-page headers (invoices / jobs / estimates / listings).
- SenderBadge in the Calendar event-detail modal.
- Bulk Select All gated by the per-row matrix.
asset_participantstriggers for estimates + PM Phase 2 / 3.- View-As:
ctx.user/ctx.profilenow swap to the impersonated admin (closes a class of impersonation bugs where session-id and impersonation-id diverged). - Post-G horizon documented (venture layer + federation), keeping the role-collapse plan's line of sight on the next two stages.
Synthetic-tests + Terraform + affiliate + polish
- Daily nightly synthetic testing infrastructure (PLAN v2).
- Terraform IaC for Vercel + Cloudflare + Supabase.
- Affiliate: rep-led prospect invite (the reduced Phase 5.6).
- Sender-program labels: "Affiliate" → "Sales Rep" when
program_type=sales_partner. - Star a tab to make it the default landing page (Invoices / Estimates).
- Inline star on every gallery tab (not just settings).
- Dev-mode hotfix: calendar + admin-context fetches were broken in local dev; CSP override in middleware drops
upgrade-insecure-requests.
Housekeeping
- Day 9 partial-UNIQUE migration applied to prod.
- One-off debugging helpers committed to
scripts/dev. - Admin federations plan + CKB white-label handoff doc written.
Day 131 - Saturday, May 23, 2026
The first nightly synthetic suite goes live and earns its keep on day one: a prod 500 on /api/admin/listings was caught by it before any user reported it. Also: lint baseline drops from 457 → 275 in one sweep, and the em / en-dash ban becomes machinery rather than a memory.
Morning - synthetic Phase 2 + the lint cliff
- Phase 2 - order-confirm side-effects test.
- Phase 2 waves 1-4 - broad coverage expansion.
- Cleared the
no-unused-varssweep: ESLint baseline 457 → 275 in one commit. - R1 test checklist + D-roll automation scripts.
Mid-morning - role-collapse R1 plays catch-up
- Customer-side reads now use service-role for owner-only tables (the RLS gap was breaking detail pages).
- Listing resources tabs open for non-owner viewers.
requireAdmin()is now impersonation-aware.- Hide order-edit affordances + clean Products Gallery copy.
- Theme selector editable for non-owners too.
- Three more View-As surfaces fixed: gallery-types, features, notifications.
Afternoon - the em-dash ban becomes machinery
- Every prose-generating AI prompt in the repo now imports the em / en-dash ban. The
AI_STYLE_RULESrule referenced in CLAUDE.md becomes literal code wired into the prompt path, not just convention.
Evening - drift + prod bug + Supabase CLI pin
- Service API drops the dropped
service_typecolumn fromservicesSELECT. - Synthetic-tests cookie format updated for Supabase SSR v0.5+ (was silently breaking mutations on the suite).
jobs.property_zipNOT NULL dropped (real customers don't always have a property zip).- Supabase CLI pinned to 2.98.0 + atomic write in
db:types(the unpinned version was randomly breaking generated types). - Prod bug surfaced by synthetic:
/api/admin/listings GET500 - clients schema drift. Sealed.
Day 132 - Sunday, May 24, 2026
Two foundational invariants land: the date / time picker components become canonical (native HTML widgets banned in new code), and D-final ships - profiles.role CHECK now contains only admin + superadmin. The role-collapse migration crossed its event horizon today.
Morning - listings 500 root cause + drift hub
- Surfaced 500-error detail in the response body to isolate the breakage.
- Bypassed the broken
buildAdminScopedQueryhelper + used the actual schema columns directly. - Introduced a code-vs-schema drift log; wired it into CLAUDE.md and its siblings; consolidated into the existing audit hub a few commits later.
- Allowlisted synthetic test scripts and 22 other files for service-role usage in the security scanner.
Mid-day - jobs detail money column + calendar render
/admin/jobs/[id]- Subtotal $0 + missing Tax row fixed by reading the summary from line items rather than the cached metadata field.- Listings: shoot date on cards, scoped sender badge, persistent status filter.
- Canonicalized the date / time picker components and banned native HTML widgets in new code (the native time-picker was visually wrong on Carlos's machine for weeks).
- Calendar WeekView now renders the full 24h, with the initial viewport scoping to business hours.
Afternoon - AI Tour Video + AI Edit operability
- Veo Developer API rejected
resizeMode; switched to asharppre-crop upstream. - Customer cost surfaced in the modal + retries=0 cost protection (don't charge an admin twice for the same generation).
- Completed tours surfaced in admin billing history.
- AI Edit: watchdog cron for stuck jobs introduced, then reverted, then replaced by slime-mold self-healing - no watchdog, the rows heal themselves on the next access.
Evening - D-final (the role-collapse event horizon)
account_lifecyclemoved fromadminstoprofiles(where it belongs - it's a person attribute, not an admin-row attribute).- D-rolling complete: 15 / 15 legacy
client/team_memberprofile rows rolled toadminplus theirasset_participantsedges. - D-final shipped:
profiles.roleCHECK shrunk toadmin/superadminonly. - Calendar-emails stop linking clients to the
/clientdashboard (which no longer exists for them). - Public-reschedule: calendar events get their own
public_tokenand share theorders/[token]channel. - Marketing copy: "Living Ecosystem" body updated to Carlos's spec.
Day 133 - Monday, May 25, 2026
71 commits. A very heavy day. Major arcs:
- Inngest operability conventions rolled out to ~32 functions across three batches (AI editing trio, then Stripe / payment workers, then 26 outbound email / SMS workers).
- Campaigns module ships from schema → launcher → billing wire-up in a single day.
- Role-collapse Phase E (team-member portals archived) + Phase F.1 - F.4 (
active_uid+ RLS sweep + claim infrastructure + claim RPC + UX + middleware) all land. - Security: rate-limit + Turnstile on
/loginand/forgot-password, auth-anomaly cron, Sentry, distributed brute-force detector. - CRM polish, notifications bounce-feedback, superadmin email-failures dashboard, setup industry picker rewrites.
Pre-dawn - listings POST + an audit-driven test catches 2 prod bugs
- Listings POST handler must generate
listing_number(NOT NULL, no default). - Drops the non-existent
titlecolumn from POST. - Audit-driven test on
accept_team_invitationcatches two prod bugs review-by-eye missed (validating the "tests after review" heuristic). asset_participantssynthetic-test column drift fixed (entity_*notasset_*).
Morning - Inngest operability conventions (batches 1-3)
The new convention (withTimeout + sanitizeError + structured emitter payloads) gets applied to:
- AI editing trio (batch 1).
- 4 Stripe / payment workers (batch 2).
- 26 outbound email / SMS workers (batch 3).
The motivation is operational: when a worker dies mid-run we want a clean log line, not a partial half-state. Slime-mold self-healing already handles the data layer; operability is the same idea at the worker boundary.
Mid-morning - Campaigns module ships
- v3.2 schema scaffold - 10 migrations + scoping doc.
- v3.3 launcher + nav + Inngest fan-out + tests.
- G4 unblocked - source-object launchers on listings + galleries.
- G10 - pricing-engine purpose filter sweep + v3.3 plan defer.
- Moved to premium tier (default-off opt-in).
- Billing wire-up - Stripe invoice-items on overage.
- Per-source campaigns history sections + product launcher.
- Drop white-label hard gate + hero image + editable templates.
- Renamed "Listing Announcement Templates" → "Gallery Templates" (industry-neutral copy).
- Empty-audience send guard (server + segment-mode UI).
- Listing-context launcher with announcement presets.
- Segment purpose badge + Use-in-Campaigns toggle.
- Row actions (Stop / Hide / Delete) + dispatch-time cancel guard.
- Rich text editor with image upload +
bodyHtmlsanitize.
Afternoon - role-collapse Phase E + Phase F
- Phase E: team-member portals archived (the
/teamroute surface goes the same way/clientdid Saturday). - Phase E orphan duplicates dropped + plan doc + drift entry.
- Phase E config edits locked in.
- Phase F plan v0.3 + RLS inventories + dry-run.
- Phase F.1 + F.2 migrations: introduce
active_uid()SQL function + RLS sweep that uses it. Applied to prod +db:typesrefreshed. - Phase F.3 + F.4 migrations: claim infrastructure + claim RPC. Applied to prod.
/api/auth/claim-relationshipsroute handler.- Claim-identity UX + middleware bounce.
- Phase F test suite + dismiss endpoint (audit folds).
Late afternoon - security uplift
- Rate-limit + Turnstile on
/loginand/forgot-password. - Auth-anomaly alerting cron + Sentry error tracking.
- Distributed brute-force detector + Sentry release tag + e2e verify script.
- Cleanup crons for
webhook_processed_events+platform_email_failures.
Evening - CRM, notifications, setup, marketing polish
- CRM: active-tab highlight in the sub-nav; one-line intros + example empty states on every CRM tab; examples always visible (collapsible) - not gated on empty; auto-derive template slug from name;
CrmHelpExampleon the four remaining tabs. - Notifications: transactional email-bounce notifications (MVP slice); atomic throttle RPC + webhook dedup short-circuit (Codex audit fold); opt-out toggle.
- Setup: slime-mold framing + galleries reword + campaigns surfaced on the feature questionnaire; step 5 v3 -
estimatesfeature_key+ industry-driven feature defaults; step 5 industry picker + nested gallery sub-types;improve_digital_footprintpre-enablesweb_builderon step 5; step 5 industries vocabulary swap to 20 NAICS sectors + search-filter UI. - Marketing: convert specialist-trap quotes to a bulleted list; apply homepage hierarchy proposal; parallax industry chips + card-shaped sections so the globe stays visible; hide parallax chips at page load until user starts scrolling.
- Block letters from the phone field at input time on signup.
completeAdminSignupmust setprofile.account_lifecycle='active'(coherence-trigger fix).- Invoices: silent misdelivery on person-change + calendar modal lifecycle pill; tighten cascade - handle client-unset, fail-loud on missing
adminId. - Refactor: split Listings Resources into Setup + Activity.
- AI tour video metered billing - real Stripe invoice items (not just a metric).
- Superadmin:
platform_email_failuresdashboard - list + resolve action. - Services audit - resumed alphabetical table walk; self-corrected Finding #4 as a polymath landmine, not redundancy.
- Build heap bumped to 12 GB + propagated via pre-push hook.
Day 134 - Tuesday, May 26, 2026
Phase G of role-collapse verifies clean against prod (66 / 66 passing) and Phase E ships its final piece - the /client/* portal surface is archived, ending a route tree that's been load-bearing since Day 1.
Pre-dawn - backup / DR posture
docs/security/gets a backup and recovery posture doc + a monthly smoke-test script.
Morning - role-collapse Phase G + Phase E final
- Phase G verification: 66 / 66 tests passing against prod.
- Phase E final:
/client/*portal archived. - Phase E audit residuals - login default + notification link cleanup.
Mid-day - Campaigns audience picker + segments
- Listing-launcher audience picker + loud-fail error surface.
- Listing-launcher drops
asset_participantsin favor of explicit chips, adds email paste. - Chip-input + CRM autocomplete + empty-segment CTA.
- Mirror the canonical client picker pattern + "+ Create new segment".
- Humanize the source line on campaign detail + hide UTM stem behind a disclosure.
Afternoon - setup / onboarding / synthetic
- Drop industries concept entirely - anyone can be anything. (The 20-NAICS-sector picker from yesterday is gone; the presence of a "what do you sell" picker re-frames identity in a way that conflicts with the platform's "users evolve" thesis.)
- Banner-driven onboarding - no longer forces
/setupon every first-login. /setupreachable for existing admins + audit fixes.- Synthetic:
SYNTHETIC_TEST_TOKENbypass + admin-auth nightly suite + complete editor seeding inseed-prod.mjs. - Tests Phase 2 mechanical refactors - env-driven Origin +
BASE_URL. - CI: synthetic-public-only nightly run wired.
- Un-ignored
Marcott Migration/tests/so the Playwright suite is tracked.
Evening - small fixes, big blast radius
- Pinned
isomorphic-dompurifyto 2.25.0 (unblocked prod admin pages - a 2.26 release shipped a regression). - Closed Turnstile bypass in
/api/auth/send-verification. - Date-gated reminders mechanism documented (the new CLAUDE.md section); 2026-08-26 backup-DR follow-ups filed.
- Early-adopter welcome banner on the admin dashboard.
- Inngest Batch 4 operability - six external-sync workers (the next phase of Monday's rollout).
Day 135 - Wednesday, May 27, 2026
White-Label gets the Phase 2 audit-and-fix pass it has been carrying as risk for a month. Email sending now propagates the WL identity across every call site (14 missed sites patched + 3 PM-specific ones), and Stage 1 + Stage 2 of the Gmail-user path for purchased domains ship - the path where an admin who only owns a Gmail address can run their business under a real domain we buy on their behalf.
Morning - consolidation plan + synthetic alignment
- Consolidation plan series v1 - v5 + Phase 0 scaffolding written.
- Synthetic Playwright specs aligned with current app state.
White-Label Phase 2 - propagate WL across every send site
- 14 missed call sites now send with the WL identity.
- 3 PM sites missed in the initial Phase 1 audit patched.
- Decouple the email From local-part from the web subdomain (one admin, one apex; many local-parts allowed).
- From-header regex aligned + test-email wired to WL.
- Notify the admin when a purchased WL domain finishes registering.
- Email Sender Preview reflects the verified WL From.
- Reorder buy / connect sections in WL settings; persist DNS records so an admin can come back and see what they configured.
- Email admin on attach-started + verify-attempted (the "we're working on it" + "you should check your DNS" prompts).
- Move setup emails to event-triggered Inngest workers (so they fire reliably, not on cron drift).
- Em-dashes removed from user-facing WL copy (catching the ones that slipped past Saturday's machinery).
Evening - Gmail-user path (purchased domains) Stages 1 + 2
- Stage 1: Block free-email providers (Gmail / Yahoo / Outlook.com / etc.) as a WL apex. The WL apex must be a domain we can configure DNS on; a
@gmail.comuser must buy a real domain through us first. - Stage 2: Inbound email forwarding for purchased domains. Replies to the admin's new
support@theiractualdomain.comget forwarded to whatever inbox they already use (often the Gmail they signed up with). Closes the "I bought the domain, now where do replies go?" loop.
Day 136 - Thursday, May 28, 2026
A feature day. Two big new things land: a US Copyright Office registration service (the prep-kit + filing wizard for admins to file their own work) and Fold - a new umbrella brand for galleries - ships Phase 1 (display-only).
Morning - visual identity
- New atom brand mark; reserve Sparkles for AI surfaces only. The icon system was getting muddled - atoms for brand, sparkles for "this is AI".
Mid-day - US Copyright Office registration service
- Initial cut of the copyright registration service (open to all admins, not gated by the CopyPro subscription per Carlos's earlier directive).
- Image-level entry + "registered" badge in gallery cards.
- Confirm payment on the success redirect - don't rely only on the webhook, which can race.
- Surface pending filings on the superadmin dashboard.
- Add Copyright link to the superadmin sidebar nav.
- Phase 9: encrypted filing profile + full GRPPH fields. The personal-legal-info table that the
copypro_filing_profilesrule in CLAUDE.md guards - encrypted at rest, only decrypted server-side for the owning admin. - Wizard screenshot / walkthrough script + magic-link dev tools added.
Afternoon - affiliate + Inngest docs
- Affiliate: SalesPro relabel + sales-rep guide page.
- Inngest: expanded conventions doc + emitter payload audit.
Evening - Fold rebrand (Phase 1, display-only)
- Galleries umbrella rebranded to "Fold" - display-only Phase 1, the same pattern as the CRM rename. Routes / code / DB stay
galleriesinternally. - BrandMark used for success states; Fold hero copy; brand concept assets imported.
- Domains: source the buy-domain TLD list from a single Cloudflare-sellable allowlist (no more drift between the buy-flow and what we can actually sell).
NOTIFY pgrstadded to the distributed brute-force view migration (PostgREST schema cache wasn't picking up the new view).- Superadmin sidebar made scrollable so nav items don't overflow.
- Reminders + native-apps desktop plan refreshed.
- Diagnostic queries for archive + image-derivatives added to
scripts/dev.
Day 137 - Friday, May 29, 2026
The Fold rebrand gets walked back the same week it shipped. "Galleries" labels are restored across admin menus / UI. The internal "Fold" naming stays as a brand concept in mockups/foldverse/ and as Carlos's working name, but the live product reverts to the term users were already searching for. A reminder that display-only renames have a real UX cost even when the code stays untouched.
The day also picks up a major performance + security pass on RLS (33 policies rewritten so Postgres can plan around the auth lookup) and commercial gallery preview bulk download finally routes through the canonical pipeline instead of its own one-off code path.
Morning - revert Fold + RLS perf sweep
- Reverted "Fold" labels across admin menus / UI. The rebrand stays internal; the brand concept lives in
mockups/foldverse/. - RLS perf: wrapped bare
auth.uid()/auth.email()in(select …)across 33 policies. The(select …)wrap lets Postgres treat the auth lookup as a constant per query rather than re-evaluating per row. - Phase 2 RLS scoping doc written + hardened after Codex + Gemini review (TO-public → TO-authenticated tightening).
security_invokerset on thedistributed_brute_forceview (yesterday's RLS posture finally consistent - the view was running as creator).
Mid-day - gallery repairs + admin perf
- Commercial gallery: download, delete, and selection checkbox all repaired in one pass.
- Commercial gallery preview bulk download routed through the canonical pipeline (was using its own one-off code path).
- Admin: parallelize page queries + lazy-load charts + add loading skeletons. The dashboard felt slow because it was sequential; the queries are independent, so they can run in parallel.
Afternoon - IDOR scanner false positives + brand assets
- IDOR scanner taught about
active_uid()(the Phase F SQL helper) - clears 133 false-positive alerts. The scanner was treating the new helper as an unscoped read. - Foldverse brand / dashboard / homepage mockups + a render script committed to
mockups/foldverse/(Carlos's brand-direction sandbox stays there even though the live product reverted). - ESLint: ignore the throwaway
_tmp-*scratch files (they were polluting the lint baseline).