feat: UX & notification improvements — icons, events, chat names, link validation, templates
- Show entity icons on all cards with fallback defaults (providers, trackers, targets, bots)
- Enrich EventLog with provider_name, tracker_name, assets_count; add DB migration
- Dashboard events: filtering (type, provider, search), sorting, pagination, dynamic page size
- Friendly chat names on telegram target cards (resolve from TelegramChat table)
- Test message button on bot chat items with locale-aware messages
- Album public link validation on tracker save with auto-create dialog
- Support albums without public links: conditional <a href> in templates
- Fetch shared links during poll, enrich events with public_url/protected_url
- Per-asset public_url in template context ({share_url}/photos/{asset_id})
- Common date/location detection: common_date + common_location context vars
- Dual date formats: date_format (datetime) + date_only_format (date only)
- Template clone button, HTML link rendering in template preview
- Fix Telegram asset download 401: pass x-api-key headers through client
- Fix provider external_url matching for API key scoping
- Fix event timestamp timezone (append Z suffix for UTC)
- Localize event filter controls, test messages (EN/RU)
- Template variable UI helpers updated with all new fields
- CLAUDE.md: template system sync rules documentation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# Feature Context: UX & Notification Improvements
|
||||
|
||||
## Current State
|
||||
Starting implementation. All entity models already have `icon: str` fields. EventLog exists with basic fields. Immich client already has shared link CRUD methods. Frontend uses Svelte 5 + Tailwind v4 with inline form/card pattern on all CRUD pages.
|
||||
|
||||
## Temporary Workarounds
|
||||
- None yet
|
||||
|
||||
## Cross-Phase Dependencies
|
||||
- Phase 3 depends on Phase 2 (enriched event data from API)
|
||||
- Phase 6 logically follows Phase 5 (link validation informs no-link handling)
|
||||
- Phases 1, 4 are fully independent
|
||||
|
||||
## Implementation Notes
|
||||
- All overlays MUST use `position: fixed` with inline styles and `z-index: 9999`
|
||||
- SQLAlchemy async + aiohttp: eager load DB data before aiohttp context
|
||||
- Jinja2 templates use SandboxedEnvironment
|
||||
- Icons stored as MDI icon path names (e.g., `mdiCamera`) from `@mdi/js`
|
||||
- Frontend uses MdiIcon component to render SVG icons
|
||||
@@ -0,0 +1,44 @@
|
||||
# Feature: UX & Notification Improvements
|
||||
|
||||
**Branch:** `feature/ux-notification-improvements`
|
||||
**Base branch:** `feature/entity-relationship-refactor`
|
||||
**Created:** 2026-03-20
|
||||
**Status:** 🟡 In Progress
|
||||
**Strategy:** Incremental
|
||||
**Mode:** Automated
|
||||
**Execution:** Orchestrator
|
||||
|
||||
## Summary
|
||||
Seven UX and notification improvements: show entity icons, enrich event data, dashboard filtering/sorting, friendly Telegram chat names, bot test messages, album public link validation, and graceful degradation for albums without public links.
|
||||
|
||||
## Build & Test Commands
|
||||
- **Build (backend):** `cd packages/server && pip install -e .`
|
||||
- **Build (frontend):** `cd frontend && npx vite build`
|
||||
- **Test (backend):** `cd packages/server && python -m pytest` (if tests exist)
|
||||
- **Lint:** N/A
|
||||
|
||||
## Phases
|
||||
|
||||
- [ ] Phase 1: Show Entity Icons on Cards [domain: frontend] → [subplan](./phase-1-entity-icons.md)
|
||||
- [ ] Phase 2: Enrich Event Data [domain: backend] → [subplan](./phase-2-enrich-events.md)
|
||||
- [ ] Phase 3: Richer Events Display + Filtering & Sorting [domain: frontend] → [subplan](./phase-3-events-ui.md)
|
||||
- [ ] Phase 4: Friendly Chat Names + Test Message for Bots [domain: fullstack] → [subplan](./phase-4-chat-names-test-msg.md)
|
||||
- [ ] Phase 5: Album Public Link Validation [domain: fullstack] → [subplan](./phase-5-link-validation.md)
|
||||
- [ ] Phase 6: Support Albums Without Public Links [domain: backend] → [subplan](./phase-6-no-link-support.md)
|
||||
|
||||
## Phase Progress Log
|
||||
|
||||
| Phase | Domain | Status | Review | Build | Committed |
|
||||
|-------|--------|--------|--------|-------|-----------|
|
||||
| Phase 1: Entity Icons | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
| Phase 2: Enrich Events | backend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
| Phase 3: Events UI | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
| Phase 4: Chat Names + Test | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
| Phase 5: Link Validation | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
| Phase 6: No-Link Support | backend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
|
||||
## Final Review
|
||||
- [ ] Comprehensive code review
|
||||
- [ ] Full build passes
|
||||
- [ ] Full test suite passes
|
||||
- [ ] Merged to `feature/entity-relationship-refactor`
|
||||
@@ -0,0 +1,44 @@
|
||||
# Phase 1: Show Entity Icons on Cards
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** frontend
|
||||
|
||||
## Objective
|
||||
Display the user-selected icon on every entity card (Providers, Trackers, Targets, Telegram Bots). Currently the IconPicker saves the icon but cards use hardcoded default icons.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Task 1: Update provider cards in `frontend/src/routes/providers/+page.svelte` to show `provider.icon` (fallback to default server/cloud icon)
|
||||
- [ ] Task 2: Update tracker cards in `frontend/src/routes/trackers/+page.svelte` to show `tracker.icon` (fallback to default radar icon)
|
||||
- [ ] Task 3: Update target cards in `frontend/src/routes/targets/+page.svelte` to show `target.icon` (fallback to type-based default)
|
||||
- [ ] Task 4: Update bot cards in `frontend/src/routes/telegram-bots/+page.svelte` to show `bot.icon` (fallback to robot icon)
|
||||
- [ ] Task 5: Ensure icon rendering uses MdiIcon component with proper sizing consistent with existing card headers
|
||||
|
||||
## Files to Modify/Create
|
||||
- `frontend/src/routes/providers/+page.svelte` — card header icon
|
||||
- `frontend/src/routes/trackers/+page.svelte` — card header icon
|
||||
- `frontend/src/routes/targets/+page.svelte` — card header icon
|
||||
- `frontend/src/routes/telegram-bots/+page.svelte` — card header icon
|
||||
|
||||
## Acceptance Criteria
|
||||
- Each entity card displays the saved icon if set
|
||||
- Falls back to a sensible default icon per entity type when no icon is saved
|
||||
- Icon styling is consistent across all card types
|
||||
- No regressions in existing card layout or functionality
|
||||
|
||||
## Notes
|
||||
- Icons are stored as MDI path constant names (e.g., the string key from `@mdi/js`)
|
||||
- The MdiIcon component already exists in `frontend/src/lib/components/MdiIcon.svelte`
|
||||
- IconPicker component already handles icon selection and stores the value
|
||||
- Need to check exactly how icon values are stored (full path data vs key name) to render correctly
|
||||
|
||||
## Review Checklist
|
||||
- [ ] All tasks completed
|
||||
- [ ] Code follows project conventions
|
||||
- [ ] No unintended side effects
|
||||
- [ ] Build passes
|
||||
- [ ] Tests pass (new + existing)
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in after completion -->
|
||||
@@ -0,0 +1,45 @@
|
||||
# Phase 2: Enrich Event Data
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** backend
|
||||
|
||||
## Objective
|
||||
Enrich EventLog with provider name, tracker name, and asset counts so the dashboard can display richer event details. Update the status API to return these fields.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Task 1: Add `provider_name`, `tracker_name`, `provider_id` columns to EventLog model (or store in details JSON to avoid migration complexity)
|
||||
- [ ] Task 2: Update watcher.py event logging to populate provider_name, tracker_name, and assets_count when creating EventLog entries
|
||||
- [ ] Task 3: Update status.py GET /api/status endpoint to return enriched event fields (provider_name, tracker_name, event_type, collection_name, assets_count, details)
|
||||
- [ ] Task 4: Add pagination/limit support to the events endpoint (query param `limit`, default 20)
|
||||
- [ ] Task 5: Add optional filtering query params: `event_type`, `provider_id`, `search` (name match)
|
||||
- [ ] Task 6: Handle migration for existing EventLog rows (backfill from tracker/provider if possible, or leave empty for old rows)
|
||||
|
||||
## Files to Modify/Create
|
||||
- `packages/server/src/notify_bridge_server/database/models.py` — EventLog model changes
|
||||
- `packages/server/src/notify_bridge_server/services/watcher.py` — populate new fields on event creation
|
||||
- `packages/server/src/notify_bridge_server/api/status.py` — enrich response, add filtering/pagination
|
||||
- `packages/server/src/notify_bridge_server/database/migrations.py` — add columns if using real columns
|
||||
|
||||
## Acceptance Criteria
|
||||
- EventLog entries created after this phase include provider_name, tracker_name, assets_count
|
||||
- GET /api/status returns enriched event data
|
||||
- Filtering by event_type, provider_id, and text search works
|
||||
- Old events without new fields still render (graceful degradation)
|
||||
- No breaking changes to existing API consumers
|
||||
|
||||
## Notes
|
||||
- Storing provider_name/tracker_name as denormalized strings is intentional — the event log should be a historical record even if the tracker/provider is later deleted
|
||||
- The `details` JSON field already exists and could hold extra data, but explicit columns are better for filtering
|
||||
- EventLog.tracker_id already exists as FK — can join for backfill but also store name directly
|
||||
|
||||
## Review Checklist
|
||||
- [ ] All tasks completed
|
||||
- [ ] Code follows project conventions
|
||||
- [ ] No unintended side effects
|
||||
- [ ] Build passes
|
||||
- [ ] Tests pass (new + existing)
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in after completion -->
|
||||
@@ -0,0 +1,43 @@
|
||||
# Phase 3: Richer Events Display + Filtering & Sorting
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** frontend
|
||||
|
||||
## Objective
|
||||
Update the dashboard to display richer event details (provider name, tracker name, album name, event type, assets count) and add filtering/sorting controls.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Task 1: Update event timeline items in `+page.svelte` to show: provider name, tracker name, album name, event type badge, and assets count
|
||||
- [ ] Task 2: Add filter controls above the events list: text search input, event type dropdown (all/assets_added/assets_removed/collection_renamed/collection_deleted/sharing_changed), provider dropdown (populated from providers list)
|
||||
- [ ] Task 3: Add sort control: newest first / oldest first toggle
|
||||
- [ ] Task 4: Wire filters to API query params (event_type, provider_id, search) from Phase 2
|
||||
- [ ] Task 5: Add "load more" button or increase default limit for events
|
||||
- [ ] Task 6: Ensure graceful display when enriched fields are empty (old events before Phase 2)
|
||||
|
||||
## Files to Modify/Create
|
||||
- `frontend/src/routes/+page.svelte` — event display, filter/sort controls, API calls
|
||||
|
||||
## Acceptance Criteria
|
||||
- Each event shows: provider name, tracker name, album name, event type (badge), assets count
|
||||
- Filtering by text search, event type, and provider works
|
||||
- Sort by time (newest/oldest) works
|
||||
- Old events without enriched data display gracefully (show what's available)
|
||||
- Filter/sort state resets on page load (no persistence needed)
|
||||
- UI is responsive and consistent with existing design
|
||||
|
||||
## Notes
|
||||
- Depends on Phase 2's enriched API response
|
||||
- Provider list for the dropdown can come from existing /api/providers endpoint
|
||||
- Event type badges already have color mapping in the current dashboard code
|
||||
|
||||
## Review Checklist
|
||||
- [ ] All tasks completed
|
||||
- [ ] Code follows project conventions
|
||||
- [ ] No unintended side effects
|
||||
- [ ] Build passes
|
||||
- [ ] Tests pass (new + existing)
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in after completion -->
|
||||
@@ -0,0 +1,50 @@
|
||||
# Phase 4: Friendly Chat Names + Test Message for Bots
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** fullstack
|
||||
|
||||
## Objective
|
||||
Two Telegram UX improvements: (a) show friendly chat names instead of raw IDs on target cards, (b) add a "Send Test Message" button to each chat item on the bots page.
|
||||
|
||||
## Tasks
|
||||
|
||||
### 4a: Friendly Chat Names on Target Cards
|
||||
- [ ] Task 1: Update GET /api/targets in targets.py to resolve chat_id → friendly name by looking up TelegramChat table (match on bot token's bot_id + chat_id)
|
||||
- [ ] Task 2: Include `chat_name` field in target API response alongside chat_id
|
||||
- [ ] Task 3: Update target cards in targets/+page.svelte to display "Chat Name (chat_id)" instead of raw chat_id
|
||||
|
||||
### 4b: Test Message Button for Bot Chats
|
||||
- [ ] Task 4: Add POST /api/telegram-bots/{bot_id}/chats/{chat_id}/test endpoint in telegram_bots.py that sends a simple test message via the bot
|
||||
- [ ] Task 5: Add "Send Test" button to each chat item in telegram-bots/+page.svelte with loading/success/error feedback
|
||||
- [ ] Task 6: Handle edge cases (bot can't reach chat, chat deleted, etc.) with proper error messages
|
||||
|
||||
## Files to Modify/Create
|
||||
- `packages/server/src/notify_bridge_server/api/targets.py` — resolve chat names
|
||||
- `packages/server/src/notify_bridge_server/api/telegram_bots.py` — test message endpoint
|
||||
- `frontend/src/routes/targets/+page.svelte` — display friendly names
|
||||
- `frontend/src/routes/telegram-bots/+page.svelte` — test message button
|
||||
|
||||
## Acceptance Criteria
|
||||
- Target cards show "Chat Title (chat_id)" for telegram targets where chat name is known
|
||||
- Falls back to just chat_id when no matching TelegramChat record exists
|
||||
- Test message button sends a simple "Test message from Notify Bridge" to the chat
|
||||
- Button shows loading state, then success/error feedback
|
||||
- Error messages are user-friendly (not raw API errors)
|
||||
|
||||
## Notes
|
||||
- TelegramChat stores: chat_id (string), title, chat_type, username, bot_id (FK)
|
||||
- NotificationTarget.config stores: bot_token, chat_id
|
||||
- To resolve: need to find TelegramBot by token → get bot_id → lookup TelegramChat by (bot_id, chat_id)
|
||||
- For test message: use TelegramClient directly with bot token from TelegramBot record
|
||||
- The test endpoint on targets already exists (POST /api/targets/{id}/test) — this is a NEW endpoint specifically for bot chat items
|
||||
|
||||
## Review Checklist
|
||||
- [ ] All tasks completed
|
||||
- [ ] Code follows project conventions
|
||||
- [ ] No unintended side effects
|
||||
- [ ] Build passes
|
||||
- [ ] Tests pass (new + existing)
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in after completion -->
|
||||
@@ -0,0 +1,48 @@
|
||||
# Phase 5: Album Public Link Validation
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** fullstack
|
||||
|
||||
## Objective
|
||||
When saving/updating a tracker with album selections, check if the selected albums have valid public shared links. Warn the user about missing links and offer to auto-create them.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Task 1: Add GET /api/providers/{id}/albums/{album_id}/shared-links endpoint that wraps ImmichClient.get_shared_links()
|
||||
- [ ] Task 2: Add POST /api/providers/{id}/albums/{album_id}/shared-links endpoint that wraps ImmichClient.create_shared_link()
|
||||
- [ ] Task 3: In trackers/+page.svelte, after album selection changes (on save), call shared-links endpoint for each newly selected album
|
||||
- [ ] Task 4: Show a warning dialog/section listing albums without valid public links (expired, password-protected, or missing)
|
||||
- [ ] Task 5: Add "Auto-create public links" button in the warning dialog that calls the create endpoint for each missing album
|
||||
- [ ] Task 6: Add hints explaining implications: "Public links allow anyone with the URL to view album contents" and "Albums without public links will have limited notification features (no clickable links in messages)"
|
||||
- [ ] Task 7: Allow user to proceed without creating links (dismiss warning and save anyway)
|
||||
|
||||
## Files to Modify/Create
|
||||
- `packages/server/src/notify_bridge_server/api/trackers.py` or new `providers.py` routes — shared link endpoints
|
||||
- `frontend/src/routes/trackers/+page.svelte` — validation UI, warning dialog, auto-create flow
|
||||
|
||||
## Acceptance Criteria
|
||||
- On tracker save with new albums, shared links are checked
|
||||
- Albums without valid links are highlighted with a warning
|
||||
- User can auto-create links with one click
|
||||
- User can dismiss and proceed without links
|
||||
- Hints explain security/privacy implications
|
||||
- Already-valid links are not re-created
|
||||
- Expired or password-protected links are flagged as problematic
|
||||
|
||||
## Notes
|
||||
- ImmichClient already has: get_shared_links(album_id), create_shared_link(album_id, ...), delete_shared_link(), set_shared_link_password()
|
||||
- SharedLinkInfo model has: id, key, has_password, is_expired, is_accessible
|
||||
- A "valid" link = exists AND not expired AND is_accessible (has_password is a warning, not a blocker)
|
||||
- The check should only run for NEWLY selected albums (not all albums on every save)
|
||||
- Use a modal/dialog for the warning — follows project convention of fixed-position overlays
|
||||
|
||||
## Review Checklist
|
||||
- [ ] All tasks completed
|
||||
- [ ] Code follows project conventions
|
||||
- [ ] No unintended side effects
|
||||
- [ ] Build passes
|
||||
- [ ] Tests pass (new + existing)
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in after completion -->
|
||||
@@ -0,0 +1,47 @@
|
||||
# Phase 6: Support Albums Without Public Links
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** backend
|
||||
|
||||
## Objective
|
||||
Allow tracking albums even without public links. Templates should conditionally wrap items in `<a href>` when public URLs exist, otherwise show plain text names. Telegram should still send assets to chats regardless of public link status.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Task 1: In template context building (context.py), include `has_public_url` / `album_url` / per-asset `public_url` flags so templates can conditionally render links
|
||||
- [ ] Task 2: Update default system templates (EN/RU seeds) to use `{% if album_url %}` / `{% if asset.public_url %}` conditionals — wrap in `<a href>` when URL exists, plain text otherwise
|
||||
- [ ] Task 3: Verify that Telegram notification sending (notifier.py + telegram client) sends assets via direct API download regardless of public link status (it should already work since it uses internal API URLs with api_key headers, not public links)
|
||||
- [ ] Task 4: Ensure the template context correctly distinguishes between internal API URLs (for media download) and public URLs (for user-facing links in messages)
|
||||
- [ ] Task 5: Test that events from albums without public links still generate notifications with asset media but no clickable links in the message text
|
||||
|
||||
## Files to Modify/Create
|
||||
- `packages/core/src/notify_bridge_core/templates/context.py` — add public_url flags to context
|
||||
- `packages/core/src/notify_bridge_core/providers/immich/provider.py` — ensure shared link info flows through events
|
||||
- `packages/server/src/notify_bridge_server/services/notifier.py` — verify asset sending works without public links
|
||||
- Default template seeds (wherever EN/RU templates are defined) — conditional link rendering
|
||||
|
||||
## Acceptance Criteria
|
||||
- Albums without public links can be tracked without errors
|
||||
- Notifications are sent with media assets regardless of public link status
|
||||
- Message text includes clickable links only when public URLs exist
|
||||
- Message text shows plain album/asset names when no public URL
|
||||
- Default EN/RU templates handle both cases
|
||||
- No regressions for albums that DO have public links
|
||||
|
||||
## Notes
|
||||
- Internal asset URLs use format: `{provider_url}/api/assets/{id}/original` with x-api-key header
|
||||
- Public URLs use format: `{external_domain}/share/{key}` (no auth needed)
|
||||
- Telegram client downloads via internal URLs (with headers) — this is independent of public links
|
||||
- The public URL is only relevant for the message text (human-readable links)
|
||||
- Template context already has `album_url` from event.extra — need to make it None/empty when no shared link
|
||||
|
||||
## Review Checklist
|
||||
- [ ] All tasks completed
|
||||
- [ ] Code follows project conventions
|
||||
- [ ] No unintended side effects
|
||||
- [ ] Build passes
|
||||
- [ ] Tests pass (new + existing)
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in after completion -->
|
||||
Reference in New Issue
Block a user