Commit Graph

158 Commits

Author SHA1 Message Date
poduck
61882b300f feat(mobile): Add field app with date range navigation
- Add React Native Expo field app for mobile staff
- Use main /appointments/ endpoint with date range support
- Add X-Business-Subdomain header for tenant context
- Support day/week view navigation
- Remove WebSocket console logging from frontend
- Update AppointmentStatus type to include all backend statuses
- Add responsive status legend to scheduler header

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-07 01:23:24 -05:00
poduck
46b154e957 feat: Add favicon.ico and apple-touch-icon
- Create multi-resolution favicon.ico (48x48, 32x32, 16x16) from logo
- Add apple-touch-icon.png for iOS devices
- Update index.html to use new favicon

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-06 14:32:35 -05:00
poduck
023ea7f020 feat(contracts): Add contracts permission to subscription tiers
- Add contracts_enabled field to SubscriptionPlan model
- Add contracts toggle to plan create/edit modal in platform settings
- Hide contracts menu item for tenants without contracts permission
- Protect /contracts routes with canUse('contracts') check
- Add HasContractsPermission to contracts API ViewSets
- Add contracts to PlanPermissions interface and feature definitions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-05 23:28:51 -05:00
poduck
35f4301fe1 feat(contracts): Add legal export package and ESIGN compliance improvements
- Add export_legal endpoint for signed contracts that generates a ZIP with:
  - Signed contract PDF
  - Audit certificate PDF with signature details and hash verification
  - Machine-readable signature_record.json
  - Integrity verification report
  - README documentation

- Add audit certificate template with:
  - Contract and signature information
  - Consent records with exact legal text
  - Document integrity verification (SHA-256 hash comparison)
  - ESIGN Act and UETA compliance statement

- Update ContractSigning page for ESIGN/UETA compliance:
  - Consent checkbox text now matches backend-stored legal text
  - Added proper legal notice with ESIGN Act references

- Add signed_at field to ContractListSerializer
- Add view/print buttons for signed contracts in Contracts page
- Allow viewing signed contracts via public signing URL

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 02:29:35 -05:00
poduck
6feaa8dda5 fix(i18n): Update French win-back translation
Changed "Reconquête Client" to "Réactivation des clients" for more
natural French phrasing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 18:47:51 -05:00
poduck
f084e33621 fix(i18n): Complete German helpComprehensive translations
The German helpComprehensive section had a different structure with 250
missing keys. Replaced with complete translations matching the English
structure used by HelpComprehensive.tsx.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 18:44:26 -05:00
poduck
db0165dc5e fix(i18n): Add missing 'welcome' translation key to en/es/fr.json
The HelpComprehensive.tsx uses introduction.welcome but the translation
files only had introduction.title. Added the welcome key to match
the German translation structure.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 18:27:20 -05:00
poduck
af891d7e8f fix(i18n): Convert HelpComprehensive.tsx to use translation keys
Replaced all hardcoded English text with i18n translation function calls
to enable proper internationalization. All sections now use
helpComprehensive.* translation keys that are already present in
en.json, es.json, fr.json, and de.json.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 18:15:20 -05:00
poduck
7ef255a5f1 feat(help): Add Time Blocks section to comprehensive help docs
- Add Time Blocks section to HelpComprehensive.tsx with block levels,
  types, recurrence patterns, and key features documentation
- Add complete helpComprehensive translations for en, es, fr, de
- Update HelpContracts.tsx styling
- Enhance FeaturesPage.tsx and HomePage.tsx

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 17:58:41 -05:00
poduck
29e99631c9 feat(i18n): Add time blocks translations and fix deployment
- Add comprehensive timeBlocks translations (ES, FR, DE, EN)
- Add myAvailability translations (ES, FR, DE, EN)
- Add full helpTimeBlocks guide content (ES, FR, DE, EN)
- Add contracts guide translations (ES)
- Fix DATABASE_URL env var in deploy.sh for seed_platform_plugins
- Update Contracts page and HelpContracts guide

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 17:43:03 -05:00
poduck
2d7c1dcd27 feat(time-blocks): Add seed_holidays management command
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 17:21:02 -05:00
poduck
8d0cc1e90a feat(time-blocks): Add comprehensive time blocking system with contracts
- Add TimeBlock and Holiday models with recurrence support (one-time, weekly, monthly, yearly, holiday)
- Implement business-level and resource-level blocking with hard/soft block types
- Add multi-select holiday picker for bulk holiday blocking
- Add calendar overlay visualization with distinct colors:
  - Business blocks: Red (hard) / Yellow (soft)
  - Resource blocks: Purple (hard) / Cyan (soft)
- Add month view resource indicators showing 1/n width per resource
- Add yearly calendar view for block overview
- Add My Availability page for staff self-service
- Add contracts module with templates, signing flow, and PDF generation
- Update scheduler with click-to-day navigation in week view

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 17:19:12 -05:00
poduck
cf91bae24f feat(services): Add deposit percentage option for fixed-price services
- Add deposit_percent field back to Service model for percentage-based deposits
- Reorganize service form: variable pricing toggle at top, deposit toggle with
  amount/percent options (percent only available for fixed pricing)
- Disable price field when variable pricing is enabled
- Add backend validation: variable pricing cannot use percentage deposits
- Update frontend types and hooks to handle deposit_percent field

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 13:52:51 -05:00
poduck
c7308ad167 refactor(services): Simplify deposit to single amount field
- Remove deposit_percent field (doesn't work for variable pricing)
- Make deposit_amount default to 0 (no deposit)
- Deposit now applies to both variable and fixed pricing services
- Add requires_deposit and requires_saved_payment_method as computed properties
- Simplify frontend form with single deposit amount input
- Show deposit badge in service list when deposit > 0

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 13:42:46 -05:00
poduck
7da5d55831 fix(services): Update hooks to handle variable pricing fields
- Add ServiceInput interface for create/update operations
- Transform variable pricing fields in useServices query
- Handle deposit_amount and deposit_percent in mutations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 13:35:35 -05:00
poduck
3bc8167649 feat(payments): Add variable pricing with deposit collection
Services can now have variable pricing where:
- Final price is determined after service completion
- A deposit (fixed amount or percentage) is collected at booking
- Customer's saved payment method is charged for remaining balance

Changes:
- Add variable_pricing, deposit_amount, deposit_percent fields to Service model
- Add service FK and final_price fields to Event model
- Add AWAITING_PAYMENT status to Event
- Add SetFinalPriceView endpoint to charge customer's saved card
- Add EventPricingInfoView endpoint for pricing details
- Update Services page with variable pricing toggle and deposit config
- Show "From $X" and deposit info in customer preview

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 13:33:03 -05:00
poduck
b0512a660c feat(billing): Add customer billing page with payment method management
- Add CustomerBilling page for customers to view payment history and manage cards
- Create AddPaymentMethodModal with Stripe Elements for secure card saving
- Support both Stripe Connect and direct API payment modes
- Auto-set first payment method as default when no default exists
- Add dark mode support for Stripe card input styling
- Add customer billing API endpoints for payment history and saved cards
- Add stripe_customer_id field to User model for Stripe customer tracking

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 13:06:30 -05:00
poduck
65faaae864 fix(security): Multi-tenancy isolation and customer appointment filtering
- Add request tenant validation to all ViewSets (EventViewSet, ResourceViewSet,
  ParticipantViewSet, CustomerViewSet, StaffViewSet) to prevent cross-tenant
  data access via subdomain/header manipulation
- Change permission_classes from AllowAny to IsAuthenticated for EventViewSet
  and ResourceViewSet
- Filter events for customers to only show appointments where they are a
  participant
- Add customer field to EventSerializer to create Customer participants when
  appointments are created
- Update CustomerDashboard to fetch appointments from API instead of mock data
- Fix TenantViewSet.destroy() to properly handle cross-schema cascade when
  deleting tenants

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 11:05:01 -05:00
poduck
dbe91ec2ff feat(auth): Convert login system to use email as username
- Backend login now accepts 'email' field (with backward compatibility)
- User creation (signup, invitation, customer) uses email as username
- Frontend login form updated with email input and validation
- Updated test users to use email addresses as usernames
- Updated all translation files (en, es, fr, de)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 10:38:53 -05:00
poduck
a2f74ee769 fix(customers): Auto-generate username when creating customers
The CustomerSerializer was missing a create method to generate a unique
username, causing IntegrityError when trying to create customers.

- Add first_name and last_name as write-only fields
- Remove email from read_only_fields so it can be set on creation
- Generate username from email prefix (with counter for uniqueness)
- Fall back to UUID-based username if no email provided

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 10:29:45 -05:00
poduck
9073970189 fix(i18n): Add language selector to platform UI
Restore the LanguageSelector component to the platform layout header,
allowing platform users to switch languages.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 10:12:38 -05:00
poduck
6554e62d30 fix(seo): Add noindex for platform and business subdomains
Dynamically set robots meta tag to noindex/nofollow when on any
subdomain (platform.*, demo.*, etc.). Only the root domain
marketing pages should be indexed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 10:08:47 -05:00
poduck
bd6d9144ce fix(seo): Block crawlers and add sitemap
- Set robots meta tag to noindex, nofollow (site not live)
- Update robots.txt with instructions for going live
- Add sitemap.xml with all marketing pages

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 10:01:44 -05:00
poduck
ad04e5f6ff fix(seo): Remove technical jargon and add SEO meta tags
- Replace "multi-tenant" wording with user-friendly alternatives
  - Hero subheadline: "Secure" instead of "Multi-tenant"
  - Feature title: "Enterprise Security" instead of "Multi-Tenant Architecture"
  - Updated testimonials and FAQ to remove technical references
- Add comprehensive SEO meta tags to index.html:
  - Meta description for search engines
  - Open Graph tags for social sharing
  - Twitter card meta tags
  - Canonical URL and robots directives
- Update all language files (en, es, fr, de) with consistent changes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 23:01:32 -05:00
poduck
460bf200d0 fix(i18n): Sync marketing translations across all languages
Update es.json, fr.json, and de.json to match en.json structure:
- Add missing benefits, plugins, and home sections
- Add new hero keys (badge, title, titleHighlight, visualContent)
- Add features automation and multi-tenancy sections
- Add pricing FAQ, starter/pro tiers
- Add signup address fields and payment setup
- Restructure footer with proper nesting
- Add contact page new keys (formHeading, scheduleCall)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 22:49:44 -05:00
poduck
3e8634b370 fix(i18n): Add missing About page timeline translations
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 22:27:30 -05:00
poduck
bc094f2f80 feat(i18n): Internationalize marketing pages and components
- HomePage.tsx: Add translation keys for features, testimonials, section titles
- FeaturesPage.tsx: Add translation keys for automation engine, multi-tenancy sections
- Hero.tsx: Add translation keys for headline, CTAs, trust signals, visual content
- ContactPage.tsx: Add translation keys for form headings, success messages
- PricingPage.tsx: Add translation keys for FAQ section
- PrivacyPolicyPage.tsx: Full internationalization of 15-section privacy policy
- TermsOfServicePage.tsx: Full internationalization of 16-section terms of service
- Footer.tsx & Navbar.tsx: Add translation keys for brand name, aria-labels

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 22:25:11 -05:00
poduck
c7f241b30a feat(i18n): Comprehensive internationalization of frontend components and pages
Translate all hardcoded English strings to use i18n translation keys:

Components:
- TransactionDetailModal: payment details, refunds, technical info
- ConnectOnboarding/ConnectOnboardingEmbed: Stripe Connect setup
- StripeApiKeysForm: API key management
- DomainPurchase: domain registration flow
- Sidebar: navigation labels
- Schedule/Sidebar, PendingSidebar: scheduler UI
- MasqueradeBanner: masquerade status
- Dashboard widgets: metrics, capacity, customers, tickets
- Marketing: PricingTable, PluginShowcase, BenefitsSection
- ConfirmationModal, ServiceList: common UI

Pages:
- Staff: invitation flow, role management
- Customers: form placeholders
- Payments: transactions, payouts, billing
- BookingSettings: URL and redirect configuration
- TrialExpired: upgrade prompts and features
- PlatformSettings, PlatformBusinesses: admin UI
- HelpApiDocs: API documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 21:40:54 -05:00
poduck
902582f4ba feat(platform): Redesign tenant invite modal with tier-based permissions
- Simplified UI with Email, Business Name, and Subscription Tier fields
- Added collapsible "Override Tier Limits" section with sliding animation
- Permission options match platform settings structure (Payments, Communication, Customization, Plugins, Advanced, Enterprise)
- Permissions are loaded from subscription plans or fallback to static defaults
- Custom limits/permissions only sent to backend when override is checked

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 20:45:29 -05:00
poduck
7b18637b1e feat(tenant): Add public-facing landing page for business subdomains
- New TenantLandingPage component with 'Coming Soon' message
- Shows business name derived from subdomain
- Has 'Sign In' button that goes to /login
- 'Powered by SmoothSchedule' footer
- Will be customizable later for each tenant

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 17:54:46 -05:00
poduck
3a1b2f2dd8 fix(onboarding): Change 'Go to Dashboard' to 'Go to Login'
The button after tenant creation was misleading - users need to log in first.
Changed button text and URL to explicitly point to /login.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 17:47:59 -05:00
poduck
88b54ef9e4 chore(traefik): Remove debug logging, set production log level
Wildcard subdomain routing is now working. Removed access logging
that was added for debugging.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 17:39:39 -05:00
poduck
5cdbc19517 fix(traefik): Fix HostRegexp YAML escaping for subdomain routing
In YAML single-quoted strings, backslashes are literal characters.
'\\.' was being interpreted as two backslashes + dot, not as an
escaped dot in the regex.

Changed from '\\.smoothschedule\\.com' to '\.smoothschedule\.com'

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 17:38:57 -05:00
poduck
f3a0f1f07a debug: Add access logging to Traefik
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 17:37:47 -05:00
poduck
f3951295ac fix(traefik): Remove conflicting TCP router for subdomain handling
The TCP router was intercepting wildcard subdomain traffic at the TCP layer
and sending it directly to nginx:80, bypassing HTTP routing entirely.
This caused 404 errors because nginx wasn't receiving proper HTTP routing.

Now relying on:
- TLS store's defaultGeneratedCert for wildcard certificate
- HTTP HostRegexp router for subdomain routing to nginx

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 17:34:41 -05:00
poduck
9cbf19ed1b fix(traefik): Simplify HTTP HostRegexp pattern
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 17:32:53 -05:00
poduck
88c74398e4 fix(traefik): Simplify HostSNIRegexp pattern for wildcard subdomains
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 17:29:17 -05:00
poduck
86947ab206 feat(deploy): Add selective service rebuild and --no-migrate option
- Add support for specifying services to rebuild (e.g., ./deploy.sh traefik)
- Add --no-migrate flag to skip migrations for config-only changes
- Reduce wait time from 10s to 5s

Usage examples:
  ./deploy.sh                               # Build all, run migrations
  ./deploy.sh traefik --no-migrate          # Only rebuild traefik, skip migrations
  ./deploy.sh django nginx                  # Build django and nginx, run migrations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 17:27:17 -05:00
poduck
7cc013eaf2 fix(traefik): Add TCP router with HostSNIRegexp for wildcard subdomain TLS
Add a TCP-level router using HostSNIRegexp to match unknown subdomains
at the TLS layer and terminate TLS with wildcard certificate.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 17:19:21 -05:00
poduck
a723d784cd fix(traefik): Add TLS store for wildcard subdomain routing
- Add default TLS store with wildcard certificate for unknown SNIs
- Add priority=1 to subdomain-router for catch-all behavior
- Use proper Traefik v3 HostRegexp syntax with anchors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 17:14:36 -05:00
poduck
13441d88fc fix(traefik): Use separate storage files for certificate resolvers
Separate acme.json storage for HTTP and DNS certificate resolvers
to prevent conflicts when requesting wildcard certificates.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 17:08:34 -05:00
poduck
b20fa5cfd8 fix(traefik): Update HostRegexp syntax for Traefik v3
Traefik v3 changed HostRegexp syntax from named capture groups to
standard regex. Added low priority to avoid matching specific routes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 16:59:45 -05:00
poduck
093f6d9a62 fix(traefik): Add env_file to read Cloudflare token
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 16:53:33 -05:00
poduck
5bf2fc5319 fix(traefik): Use Cloudflare DNS provider instead of DigitalOcean
DNS is hosted on Cloudflare, not DigitalOcean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 16:50:21 -05:00
poduck
33e4b6b9b5 feat(traefik): Add DNS challenge for wildcard SSL certificates
HostRegexp patterns don't work with HTTP challenge because Traefik
can't request certificates for dynamic subdomains. Switched to DNS
challenge using DigitalOcean provider for *.smoothschedule.com wildcard.

Requires DO_AUTH_TOKEN environment variable to be set.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 16:48:50 -05:00
poduck
434f874963 fix(traefik): Route tenant subdomains to nginx instead of django
The subdomain-router was incorrectly sending tenant subdomain requests
directly to Django (API server), causing 404 errors. Now routes to nginx
which serves the React SPA and proxies /api/ requests to Django.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 16:40:18 -05:00
poduck
0d3c97ea5f fix(onboarding): Improve loading indicator with elapsed time and better pacing
- Add elapsed time counter (MM:SS)
- Spread animation steps over ~30 seconds before final step
- Final step stays spinning (doesn't complete early)
- Progress bar caps at 90% until actually done, pulses on final step
- Show "Finalizing..." and helpful message during long final step
- Clear "45-90 seconds" time estimate upfront

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 16:37:34 -05:00
poduck
567fe0604a feat(onboarding): Add animated loading indicator and fix completion
- Add multi-step animated loading indicator during tenant creation
- Fix blank completion screen (was checking wrong step number)
- Auto-verify email for users accepting tenant invitations
- Show progress bar and step-by-step status during database setup

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 16:26:11 -05:00
poduck
5244e16279 fix(tenant): Defer plugin seeding until after transaction commits
The post_save signal was trying to seed plugins before the tenant's
schema migrations had completed, causing a 500 error when accepting
tenant invitations. Using transaction.on_commit() ensures the schema
and tables exist before seeding.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 16:11:51 -05:00
poduck
55cb97ca0d fix(deploy): Check if backup directory has content before restoring
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 16:05:04 -05:00