Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2c49c3d
Add PWA badge notifications, CHANGELOG, and AGENTS docs
sansari Feb 10, 2026
16a2b9b
Migrate AGENTS.md to CLAUDE.md for Claude Code support
sansari Feb 10, 2026
9e007a5
Establish documentation workflow with plans, changelog, and spec
sansari Feb 11, 2026
e9d0e7c
Add Web Push badge notifications for background badge updates
sansari Feb 11, 2026
3825b12
Fix docker entrypoint migration check, add prod data safety rule
sansari Feb 11, 2026
b5ea9db
Add rake upkeep:test_push for sending test push notifications
sansari Feb 11, 2026
fe14c15
Document production command convention using local Rails + prod DB
sansari Feb 11, 2026
e255e8d
Document DB schema and area IDs in CLAUDE.md for smoother data entry
sansari Feb 16, 2026
bdfdf86
Remove priority field from maintenance tasks
sansari Feb 17, 2026
f6de906
Add Mark Done button to tasks on dashboard and detail page
claude Feb 17, 2026
8688f77
Initial plan
Copilot Feb 17, 2026
1e08df1
Merge origin/main: remove priority field
Copilot Feb 17, 2026
046a117
Add test job to CI workflow
Copilot Feb 17, 2026
7af778c
Merge pull request #4 from sansari/copilot/sub-pr-3
sansari Feb 17, 2026
883faad
Initial plan
Copilot Feb 17, 2026
01c56b2
Add Railway deployment job to CI workflow
Copilot Feb 17, 2026
3e535cb
Update README with Railway deployment documentation
Copilot Feb 17, 2026
3032bd9
Merge pull request #5 from sansari/copilot/sub-pr-3-again
sansari Feb 17, 2026
ca1e87c
Initial plan
Copilot Feb 17, 2026
96d4fe4
Fix lint errors and test failures
Copilot Feb 17, 2026
b436197
Fix RuboCop lint errors by adding spaces inside array brackets
Copilot Feb 17, 2026
3ccdc67
Disable Layout/SpaceInsideArrayLiteralBrackets rule and fix extra emp…
Copilot Feb 17, 2026
727b10b
Merge pull request #6 from sansari/copilot/sub-pr-3-another-one
sansari Feb 17, 2026
ebebd55
Merge pull request #3 from sansari/claude/review-production-access-raAQq
sansari Feb 17, 2026
e57b577
Initial plan
Copilot Feb 17, 2026
1936a45
Replace PWA icon with home emoji 🏠
Copilot Feb 17, 2026
7d39a3c
Add documentation for PWA icon change
Copilot Feb 17, 2026
08e8504
Merge pull request #8 from sansari/copilot/change-icon-to-home-emoji
sansari Feb 17, 2026
56730f9
Bump puma from 7.2.0 to 8.0.1
dependabot[bot] Apr 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,75 @@ jobs:
- name: Lint code for consistent style
run: bin/rubocop -f github

test:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:17
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: upkeep_test
ports:
- 5432:5432
options: --health-cmd="pg_isready" --health-interval=10s --health-timeout=5s --health-retries=3

env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/upkeep_test
RAILS_ENV: test

steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true

- name: Set up database
run: bin/rails db:setup

- name: Run tests
run: bin/rails test

deploy:
runs-on: ubuntu-latest
needs: [scan_ruby, scan_js, lint, test]
if: github.ref == 'refs/heads/main' && github.event_name == 'push'

steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Install Railway CLI
run: npm i -g @railway/cli

- name: Deploy to Railway
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
run: railway up --service upkeep-web --detach

- name: Wait for deployment
run: sleep 150

- name: Verify deployment
run: |
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://upkeep-web-production.up.railway.app/up)
if [ "$HTTP_CODE" = "200" ]; then
echo "✅ Deployment successful! Health check returned 200"
echo "🚀 Application is live at: https://upkeep-web-production.up.railway.app"
else
echo "❌ Deployment verification failed. Health check returned $HTTP_CODE"
exit 1
fi

- name: Add deployment URL to summary
if: success()
run: |
echo "## 🚀 Deployment Successful" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Application deployed and verified at:" >> $GITHUB_STEP_SUMMARY
echo "**https://upkeep-web-production.up.railway.app**" >> $GITHUB_STEP_SUMMARY

6 changes: 3 additions & 3 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ inherit_gem: { rubocop-rails-omakase: rubocop.yml }

# Overwrite or add rules to create your own house style
#
# # Use `[a, [b, c]]` not `[ a, [ b, c ] ]`
# Layout/SpaceInsideArrayLiteralBrackets:
# Enabled: false
# Use `[a, [b, c]]` not `[ a, [ b, c ] ]`
Layout/SpaceInsideArrayLiteralBrackets:
Enabled: false
93 changes: 93 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Changelog

All notable changes to Upkeep are documented here.

## 2026-02-17 — Change PWA Icon to Home Emoji

Plan: [plans/008-change-pwa-icon.md](plans/008-change-pwa-icon.md)

- Updated PWA icon from red circle to home emoji (🏠)
- Replaced `public/icon.svg` with home emoji design
- Regenerated `public/icon.png` with home icon graphic

## 2026-02-10 — Web Push Badge Notifications

Plan: [plans/007-web-push-badge-notifications.md](plans/007-web-push-badge-notifications.md)

- Added server-side Web Push notifications using VAPID authentication (`web-push` gem)
- New `PushSubscription` model stores browser push subscriptions
- Badge controller now subscribes to push after notification permission is granted
- Service worker handles `push` events to show notifications and update the app badge
- `BadgeNotificationJob` runs every 12 hours, sends push when overdue/due_soon count changes
- Job retries 3 times on failure, then self-reports via push notification
- Existing client-side polling kept as complementary mechanism

## 2026-02-10 — Documentation Workflow

Plan: [plans/006-documentation-workflow.md](plans/006-documentation-workflow.md)

- Established convention: plans committed to `plans/` directory, numbered sequentially
- Changelog entries now reference their corresponding plan
- SPEC.md updated to reflect current app behavior
- CLAUDE.md documents the plan/changelog/spec workflow for future sessions

## 2026-02-10 — PWA Badge Notifications

Plan: [plans/005-pwa-badge-notifications.md](plans/005-pwa-badge-notifications.md)

- Added PWA support: web app manifest, service worker, `navigator.setAppBadge()` integration
- App icon on iOS home screen now shows a badge count when tasks are overdue or due soon
- One-time permission banner prompts user to enable notifications (required for iOS badges)
- Badge updates on page load, every 5 minutes, and when app returns to foreground

## 2026-02-10 — Railway Deployment

Plan: [plans/004-railway-deployment.md](plans/004-railway-deployment.md)

- Created private GitHub repo (`sansari/upkeep`)
- Deployed to Railway with PostgreSQL: https://upkeep-web-production.up.railway.app
- Configured single-database setup for primary + Solid Cache + Solid Queue
- Docker entrypoint loads Solid Queue/Cache schemas on first boot
- Switched from Thruster to Puma for Railway's dynamic PORT assignment
- Migrated all development data (9 areas, 9 equipment, 9 tasks, 3 supplies, 9 logs) to production via pg_dump/pg_restore

## 2026-02-10 — Dashboard Polish

Plan: [plans/003-dashboard-polish.md](plans/003-dashboard-polish.md)

- Changed dashboard window from 7 days → 30 days → **2 weeks** (14 days)
- Removed "Not Yet Scheduled" section from dashboard
- Low stock supplies only appear when their associated task is due within 2 weeks
- "All clear" state shows a calming message with a random cat gif from cataas.com
- Added **Log** page: reverse-chronological list of all completed maintenance
- Removed redundant "Dashboard" nav link (🏠 Upkeep logo links to dashboard)

## 2026-02-10 — Data Entry

Plan: [plans/002-data-entry.md](plans/002-data-entry.md)

- Added areas: Nida's Office, Guest Room
- Removed areas: Basement, Attic, Garage
- Renamed: Bathroom → Guest Bathroom
- Added equipment: Drinking Water Filter, Shower Head Filter, 5 Mini-Split ACs, 2 Compressors
- Configured maintenance schedules:
- Living Room & Bedroom mini-splits: every 6 weeks
- Studio, Nida's Office, Guest Room mini-splits: every 3 months
- Compressors: every 6 months
- Water filter: yearly
- Shower head filters: every 6 months
- Added supplies with Amazon/Multipure purchase links
- Logged initial maintenance completions

## 2026-02-10 — Initial Build

Plan: [plans/001-initial-build.md](plans/001-initial-build.md)

- Rails 8.1.2, Ruby 3.3.7, PostgreSQL 17
- Data model: Area → Equipment → MaintenanceTask → MaintenanceLog + Supply
- Read-only web dashboard (all data management via Claude Code conversations)
- Rake tasks for common operations (`upkeep:status`, `upkeep:complete_task`, etc.)
- JSON API on all controllers via `respond_to` blocks
- Tailwind CSS styling, Hotwire (Turbo + Stimulus)
- 29 tests, 71 assertions — all passing
- Seeded 10 default home areas
189 changes: 189 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# Upkeep — Agent Notes

Notes for Claude Code sessions working on this project.

## Project Overview

**Upkeep** is a personal home maintenance management app. It tracks areas of the house, equipment, recurring maintenance tasks, completion logs, and supplies.

- **Architecture**: Areas → Equipment → MaintenanceTasks → MaintenanceLogs + Supplies
- **Web app is read-only** — a status dashboard only, no forms or admin UI
- **All data management happens through Claude Code conversations** using rake tasks, Rails runner scripts, and Rails console
- **No authentication** — personal use only (user + partner)

## Tech Stack

- Ruby 3.3.7 (via rbenv), Rails 8.1.2, PostgreSQL 17
- Hotwire (Turbo + Stimulus), Tailwind CSS (tailwindcss-rails)
- Propshaft asset pipeline, importmap-rails
- Solid Queue + Solid Cache (sharing the primary database)
- Minitest for testing
- PWA with service worker, app badge, and Web Push notifications
- `web-push` gem for VAPID-based push notifications

## Key Conventions

### Data Management
- Use **rake tasks** for common operations: `rake upkeep:status`, `rake upkeep:complete_task[id]`, etc. (see `lib/tasks/upkeep.rake`)
- For complex data entry, write Ruby scripts to `tmp/` files and run with `bin/rails runner tmp/scriptname.rb` — this avoids shell quoting issues with single quotes in names like "Nida's Office"
- Never use inline `bin/rails runner '...'` with code containing single quotes
- **Always use tmp/ scripts for data entry**, never inline runner — even for simple inserts, to avoid quoting bugs

### Database Schema (for data entry)

**areas**: `id`, `name` (unique), `icon`, `position`, `is_default`

**equipment**: `id`, `area_id` (FK), `name`, `manufacturer`, `model_number`, `description`, `notes`, `purchase_date`
- Note: column is `manufacturer`, not `brand`

**maintenance_tasks**: `id`, `equipment_id` (FK), `name`, `frequency_value` (int), `frequency_unit` (string, e.g. `"months"`), `last_completed_at`, `next_due_at`, `instructions`, `notes`

**maintenance_logs**: `id`, `maintenance_task_id` (FK), `completed_at`, `notes`

**supplies**: `id`, `maintenance_task_id` (FK), `name`, `quantity_on_hand` (default 0), `quantity_per_use` (default 1), `unit_price`, `purchase_url`, `notes`

### Current Areas (production)
| id | name |
|----|------|
| 1 | Kitchen |
| 2 | Guest Bathroom |
| 3 | Bedroom |
| 4 | Living Room |
| 5 | Outdoor |
| 9 | Studio |
| 10 | Whole House |
| 11 | Nida's Office |
| 12 | Guest Room |

### Shell Environment
- The user's shell is zsh. Always prefix commands with `source ~/.zshrc &&` to ensure rbenv and PostgreSQL are on the PATH
- Without this, commands fall back to macOS system Ruby 2.6 which is incompatible

### Dashboard Behavior
- Shows tasks due within **2 weeks** (14 days) or overdue
- Low stock supplies only appear when their associated task is due within the window
- When nothing is due, shows "Nothing to do. Relax." with a random cat gif from `https://cataas.com/cat/gif`
- "Not Yet Scheduled" tasks are intentionally hidden from the dashboard

### Deploy Workflow
All changes should be **committed, pushed to GitHub, and deployed to Railway** before telling the user "done". The process:

```bash
# 1. Commit
git add <files> && git commit -m "message"

# 2. Push
git push origin main

# 3. Deploy (make sure upkeep-web service is linked)
cd ~/Code/upkeep
railway service upkeep-web
railway up --detach

# 4. Wait ~2.5 minutes for build, then verify
railway service status # should show SUCCESS
curl -s -o /dev/null -w "%{http_code}" https://upkeep-web-production.up.railway.app/up # should be 200
```

### Production Data & Commands
**Never lose production data.** Always verify that migrations are additive (CREATE TABLE, ADD COLUMN) before deploying. Never run destructive SQL (DROP, TRUNCATE, DELETE without WHERE) against production. When in doubt, ask first.

Railway has no SSH or remote console. `railway run` doesn't work because it spawns a bare subprocess that skips the shell profile, so rbenv never loads and macOS system Ruby is used instead.

**To run Rails commands against production**, use local Ruby with the production DATABASE_URL:
```bash
source ~/.zshrc && DATABASE_URL="postgresql://postgres:dNVJUrdIWOCWHsVCkgmvehjCtXGiuFTf@ballast.proxy.rlwy.net:49051/railway" bin/rails runner "RUBY CODE"
```

**For rake tasks against production:**
```bash
source ~/.zshrc && DATABASE_URL="postgresql://postgres:dNVJUrdIWOCWHsVCkgmvehjCtXGiuFTf@ballast.proxy.rlwy.net:49051/railway" bin/rails upkeep:status
```

**For raw SQL:**
```bash
source ~/.zshrc && psql "postgresql://postgres:dNVJUrdIWOCWHsVCkgmvehjCtXGiuFTf@ballast.proxy.rlwy.net:49051/railway" -c "SQL HERE"
```

For complex Ruby scripts, write to `tmp/` and run with `bin/rails runner tmp/scriptname.rb` (with the DATABASE_URL prefix) to avoid shell quoting issues.

## File Structure (Key Files)

```
app/
controllers/
dashboard_controller.rb # Main dashboard with JSON API
areas_controller.rb
equipment_controller.rb
maintenance_tasks_controller.rb
maintenance_logs_controller.rb # Log page
supplies_controller.rb
push_subscriptions_controller.rb # Web Push subscription management
models/
area.rb # has_many :equipment
equipment.rb # belongs_to :area, has_many :maintenance_tasks
maintenance_task.rb # Core model — scopes, complete!, due_status
maintenance_log.rb # Completion records
supply.rb # Inventory tracking with low_stock?
push_subscription.rb # Web Push subscription (endpoint, keys)
views/
layouts/application.html.erb # Nav, badge controller, permission banner
dashboard/index.html.erb # Main dashboard view
pwa/
manifest.json.erb # PWA manifest
service-worker.js # Service worker with push handlers
javascript/
controllers/
badge_controller.js # PWA badge + push subscription
jobs/
badge_notification_job.rb # Recurring job: push badge count changes
lib/tasks/
upkeep.rake # Rake tasks for data management
config/
routes.rb # Includes PWA routes
recurring.yml # Solid Queue recurring jobs
database.yml # Production uses DATABASE_URL for all databases
db/
seeds.rb # Default areas (10, but 3 were removed from DB)
plans/
001-initial-build.md # Plan for each major feature/change
002-data-entry.md
...
SPEC.md # Full app specification (keep in sync!)
CHANGELOG.md # All notable changes with plan references
CLAUDE.md # This file — agent instructions
```

## Railway Setup

- **Project**: upkeep
- **Services**: upkeep-web (Rails app) + Postgres
- **URL**: https://upkeep-web-production.up.railway.app
- **Environment variables**: RAILS_MASTER_KEY, RAILS_ENV=production, SOLID_QUEUE_IN_PUMA=1, DATABASE_URL (auto-set)
- **VAPID keys** for Web Push are stored in Rails credentials (encrypted), not env vars
- **Docker entrypoint** loads Solid Queue/Cache schemas into the shared database on first boot
- **Dockerfile** uses Puma directly (not Thruster) to work with Railway's dynamic PORT

## Documentation Workflow

Every functional change must update **all three** of these:

### 1. Plans → `plans/NNN-short-name.md`
- Before implementing non-trivial features, write a plan file
- Plans are numbered sequentially (001, 002, ...)
- Include: context/motivation, approach, files modified, verification steps
- **Commit the plan with the feature** — plans are part of the repo

### 2. Changelog → `CHANGELOG.md`
- Update after every functional change
- Each entry references its plan: `Plan: [plans/NNN-name.md](plans/NNN-name.md)`
- Reverse chronological order

### 3. Spec → `SPEC.md`
- Update after every functional change to reflect current app behavior
- The spec should always describe the **current** state of the app, not the original design
- Key sections to keep in sync: dashboard behavior, routes, scopes, deployment config

### 4. This file → `CLAUDE.md`
- Update with new conventions, file structure changes, or workflow notes
- This is what future Claude Code sessions read first
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ gem "stimulus-rails"
# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
# gem "bcrypt", "~> 3.1.7"

# Web Push notifications for PWA badge updates
gem "web-push"

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: %i[ windows jruby ]

Expand Down
Loading
Loading