Commit Graph

56 Commits

Author SHA1 Message Date
cesnimda 2b19bddf7b feat(security): audit batch C — data posture, retention, DP keys (#22)
CI / backend (push) Successful in 52s
CI / frontend (push) Successful in 11s
Deploy Staging / deploy (push) Successful in 24s
Security / secrets (push) Successful in 4s
Security / dependencies (push) Successful in 52s
CI / backend (pull_request) Successful in 49s
CI / frontend (pull_request) Successful in 10s
Security / secrets (pull_request) Successful in 3s
Security / dependencies (pull_request) Successful in 55s
2026-07-02 10:13:07 +02:00
cesnimda b905c93884 fix(security): audit batch B — SPA CSP + vite upgrade (#21)
CI / backend (push) Successful in 52s
CI / frontend (push) Successful in 15s
Deploy Staging / deploy (push) Successful in 45s
CI / backend (pull_request) Successful in 56s
CI / frontend (pull_request) Successful in 11s
Security / secrets (push) Successful in 4s
Security / dependencies (push) Successful in 54s
Security / secrets (pull_request) Successful in 4s
Security / dependencies (pull_request) Successful in 54s
2026-07-02 03:24:57 +02:00
cesnimda c5c33f7023 fix(security): audit batch A — validation, rate limiting, sessions (#20)
CI / backend (push) Successful in 53s
CI / frontend (push) Successful in 14s
Deploy Staging / deploy (push) Successful in 28s
Security / secrets (push) Successful in 3s
Security / dependencies (push) Successful in 59s
CI / backend (pull_request) Successful in 51s
CI / frontend (pull_request) Successful in 15s
Security / secrets (pull_request) Successful in 3s
Security / dependencies (pull_request) Successful in 56s
2026-07-02 03:22:17 +02:00
cesnimda 2056548702 feat(ai): pgvector embedding infrastructure (#19)
CI / backend (push) Successful in 51s
CI / frontend (push) Successful in 15s
Deploy Staging / deploy (push) Successful in 46s
Security / secrets (push) Successful in 4s
Security / dependencies (push) Successful in 56s
CI / backend (pull_request) Successful in 54s
CI / frontend (pull_request) Successful in 15s
Security / secrets (pull_request) Successful in 3s
Security / dependencies (pull_request) Successful in 55s
2026-07-02 03:00:06 +02:00
cesnimda d78fe601ff feat(ai): IEmbeddingProvider foundation (#18)
CI / backend (push) Successful in 52s
CI / frontend (push) Successful in 14s
Deploy Staging / deploy (push) Successful in 26s
CI / backend (pull_request) Successful in 53s
CI / frontend (pull_request) Successful in 16s
Security / secrets (push) Successful in 4s
Security / dependencies (push) Successful in 55s
Security / secrets (pull_request) Successful in 4s
Security / dependencies (pull_request) Successful in 53s
2026-07-02 02:05:57 +02:00
cesnimda 06b050a5ed perf(search): trigram indexes for sender/domain filters (#17)
CI / backend (push) Successful in 50s
CI / frontend (push) Successful in 14s
Deploy Staging / deploy (push) Successful in 29s
Security / secrets (push) Successful in 4s
Security / dependencies (push) Successful in 58s
CI / backend (pull_request) Successful in 53s
CI / frontend (pull_request) Successful in 15s
Security / secrets (pull_request) Successful in 4s
Security / dependencies (pull_request) Successful in 56s
2026-07-02 01:55:59 +02:00
cesnimda c0c1777d3f feat(search): fuzzy/typo tolerance (pg_trgm) (#16)
CI / backend (push) Successful in 48s
CI / frontend (push) Successful in 13s
Deploy Staging / deploy (push) Successful in 25s
Security / secrets (push) Successful in 3s
Security / dependencies (push) Successful in 55s
CI / backend (pull_request) Successful in 49s
CI / frontend (pull_request) Successful in 12s
Security / secrets (pull_request) Successful in 3s
Security / dependencies (pull_request) Successful in 53s
2026-07-01 22:45:10 +02:00
cesnimda 9bbab5d32a feat(search): why this matched highlights (#15)
CI / backend (push) Successful in 50s
CI / frontend (push) Successful in 14s
Deploy Staging / deploy (push) Successful in 29s
Security / secrets (push) Successful in 4s
Security / dependencies (push) Successful in 55s
CI / backend (pull_request) Successful in 47s
CI / frontend (pull_request) Successful in 12s
Security / secrets (pull_request) Successful in 3s
Security / dependencies (pull_request) Successful in 54s
2026-07-01 22:22:38 +02:00
cesnimda fcf290a83b feat(search): weight subject above body in FTS vector (#14)
CI / backend (push) Successful in 1m5s
CI / frontend (push) Successful in 23s
Deploy Staging / deploy (push) Successful in 38s
CI / backend (pull_request) Successful in 1m17s
CI / frontend (pull_request) Successful in 22s
Security / secrets (push) Successful in 4s
Security / dependencies (push) Successful in 1m2s
Security / secrets (pull_request) Successful in 4s
Security / dependencies (pull_request) Successful in 1m2s
2026-07-01 21:29:38 +02:00
cesnimda a9be48daee feat(search): relevance ranking + websearch_to_tsquery (#13)
CI / backend (push) Successful in 1m13s
CI / frontend (push) Successful in 27s
Deploy Staging / deploy (push) Successful in 44s
CI / backend (pull_request) Successful in 1m12s
CI / frontend (pull_request) Successful in 21s
Security / secrets (push) Successful in 5s
Security / dependencies (push) Successful in 1m3s
Security / secrets (pull_request) Successful in 4s
Security / dependencies (pull_request) Successful in 1m3s
2026-07-01 21:08:53 +02:00
cesnimda dfd8579899 feat(ui): discoverable-first search placeholder (#12)
CI / backend (push) Successful in 1m5s
CI / frontend (push) Successful in 22s
Deploy Staging / deploy (push) Successful in 32s
CI / backend (pull_request) Successful in 1m14s
CI / frontend (pull_request) Successful in 25s
Security / secrets (push) Successful in 4s
Security / dependencies (push) Successful in 1m16s
Security / secrets (pull_request) Successful in 5s
Security / dependencies (pull_request) Successful in 1m12s
2026-07-01 20:35:13 +02:00
cesnimda 4d6f10c707 feat(ui): reduced-motion + focus rings (#11)
CI / backend (push) Successful in 1m7s
CI / frontend (push) Successful in 22s
Deploy Staging / deploy (push) Successful in 30s
CI / backend (pull_request) Successful in 1m2s
CI / frontend (pull_request) Successful in 22s
Security / secrets (push) Successful in 4s
Security / dependencies (push) Successful in 1m4s
Security / secrets (pull_request) Successful in 5s
Security / dependencies (pull_request) Successful in 1m11s
2026-07-01 20:23:42 +02:00
cesnimda dde23c980c feat(ui): Inter + design-system preview page (#10)
CI / backend (push) Successful in 1m11s
CI / frontend (push) Successful in 28s
Deploy Staging / deploy (push) Successful in 1m9s
CI / backend (pull_request) Successful in 1m4s
CI / frontend (pull_request) Successful in 23s
Security / secrets (push) Successful in 5s
Security / dependencies (push) Successful in 1m6s
2026-07-01 20:14:51 +02:00
cesnimda e482d60b69 feat(ui): design-system v2 tokens (#9)
CI / backend (push) Successful in 53s
CI / frontend (push) Successful in 15s
Deploy Staging / deploy (push) Successful in 24s
Security / secrets (push) Successful in 4s
Security / dependencies (push) Successful in 1m22s
CI / backend (pull_request) Successful in 1m1s
CI / frontend (pull_request) Successful in 18s
Security / secrets (pull_request) Successful in 4s
Security / dependencies (pull_request) Successful in 1m6s
2026-07-01 20:01:43 +02:00
cesnimda 4ce2df0a2b docs: discovery blueprint + multi-provider design (#8)
CI / backend (push) Successful in 52s
CI / frontend (push) Successful in 14s
Deploy Staging / deploy (push) Successful in 18s
CI / backend (pull_request) Successful in 52s
CI / frontend (pull_request) Successful in 15s
Security / secrets (push) Successful in 4s
Security / dependencies (push) Successful in 55s
Security / secrets (pull_request) Successful in 4s
Security / dependencies (pull_request) Successful in 54s
2026-07-01 19:59:53 +02:00
cesnimda 9ee5d757f5 docs(changelog): note health gate + verified end-to-end pipeline (#7)
CI / backend (push) Successful in 50s
CI / frontend (push) Successful in 16s
CI / backend (pull_request) Successful in 49s
CI / frontend (pull_request) Successful in 14s
Deploy Staging / deploy (push) Successful in 15s
Security / secrets (push) Successful in 4s
Security / dependencies (push) Successful in 53s
Security / secrets (pull_request) Successful in 4s
Security / dependencies (pull_request) Successful in 53s
Co-authored-by: cesnimda <connor.babbington@cesnimda.co.uk>
Reviewed-on: #7
2026-07-01 17:20:34 +02:00
cesnimda 045be2bb81 Add post-deploy health gate to staging (#5)
CI / backend (push) Successful in 1m0s
CI / frontend (push) Successful in 19s
Deploy Staging / deploy (push) Successful in 19s
Security / secrets (push) Successful in 4s
Security / dependencies (push) Successful in 1m1s
CI / backend (pull_request) Successful in 57s
CI / frontend (pull_request) Successful in 19s
Security / secrets (pull_request) Successful in 5s
Security / dependencies (pull_request) Successful in 1m1s
2026-07-01 16:08:27 +02:00
cesnimda 11e6768d17 Fix staging deploy: ASCII-only echo (#4)
Deploy Staging / deploy (push) Successful in 21s
Security / secrets (push) Successful in 5s
Security / dependencies (push) Successful in 1m3s
CI / backend (push) Successful in 1m0s
CI / frontend (push) Successful in 18s
2026-07-01 13:16:31 +02:00
cesnimda d9a07a01b2 Fix staging auto-deploy: env write + port isolation (#3)
CI / backend (push) Successful in 1m5s
CI / frontend (push) Successful in 22s
Deploy Staging / deploy (push) Failing after 24s
Security / secrets (push) Successful in 5s
Security / dependencies (push) Successful in 1m4s
2026-07-01 12:50:12 +02:00
cesnimda 8727be9e94 Automated staging deploy: build env from secrets (#2)
CI / backend (push) Successful in 1m18s
CI / frontend (push) Successful in 27s
Deploy Staging / deploy (push) Failing after 1m5s
Security / secrets (push) Successful in 8s
Security / dependencies (push) Successful in 1m8s
2026-07-01 12:35:58 +02:00
cesnimda ae8e6b672e Git workflow, environments & CI/CD pipeline (#1)
CI / backend (push) Successful in 1m14s
CI / frontend (push) Successful in 28s
Security / secrets (push) Successful in 6s
Security / dependencies (push) Successful in 1m14s
Deploy Staging / deploy (push) Failing after 43s
2026-07-01 11:44:34 +02:00
cesnimda 8c2e52ad60 Merge security/hardening-phase4: Phase 4-7 security hardening + CI
CI / backend (push) Successful in 1m4s
CI / frontend (push) Successful in 20s
Phase 1-5 security remediation (SSRF egress guard, systemic IDOR global query
filters, cookie Secure, forwarded-header trust, error-leak/ProblemDetails, pagination
clamps, security headers/HSTS, open-redirect, info-leak, non-root container, Postgres
lockdown), the Critical destructive-action UX safety fix, and a Gitea CI pipeline.
Re-validated safe to merge; 39 tests green. Deferred: CSRF token, key encryption,
CSP enforce, AI-egress governance. Ops follow-up: rotate Google OAuth secret, set
strong POSTGRES_PASSWORD.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.1.0
2026-07-01 00:33:20 +02:00
cesnimda d38bcf766c docs: track project rules (AGENTS.md) + accent-swappable UI note
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 00:33:19 +02:00
cesnimda 94529df775 ci: add Gitea Actions build + test pipeline
Real CI at .gitea/workflows/ci.yml (Gitea reads .gitea/workflows, not the empty
untracked .git/workflows/ci.yml cruft): backend dotnet build + dotnet test (39 tests)
and frontend npm ci + build, on pushes to main/develop and all PRs. Establishes the
merge gate recommended in the Phase 7 git-workflow review.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 00:33:19 +02:00
cesnimda 1e15b84dce fix(search): move pagination clamp to controller (fixes cleanup regression)
Phase 5 re-validation caught a functional regression: the V-10 clamp in
SearchService.SearchAsync also capped CleanupService's internal target resolution
(pageSize 10000 -> 200), silently limiting bulk cleanup-by-query to 200 emails.

The clamp belongs at the user-facing trust boundary, not the shared service: move
MaxPageSize (200) enforcement into SearchController (both the POST body and GET query
paths). Internal callers of ISearchService now request large pages unhindered, while
user requests are still bounded. Adds a regression test proving SearchService returns
a 250-row page uncapped. No security regressions per Phase 5. All 39 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 00:29:45 +02:00
cesnimda c3bca051ea fix(ux): confirm + feedback for destructive bulk actions
Resolves the Critical destructive-action safety gap (and the CSRF-adjacent risk of
frictionless Gmail mutation):

- Bulk Trash now requires an explicit confirmation dialog (count + 30-day-recovery
  note) instead of firing on one click.
- Every bulk action surfaces a success / partial-failure / error toast.
- Rows are removed from the list only when the server confirms the whole batch
  succeeded; partial failures leave the list intact so the user can retry, instead
  of optimistically hiding failed items.

Built on the new Dialog/Toast primitives (also restyles the toolbar to the new
system). Real cross-session Undo is deferred to the activity-log backend (specced).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 00:22:43 +02:00
cesnimda e6a0239436 fix(security): systemic IDOR safeguard via EF global query filters
Defense-in-depth tenant isolation: every user-owned entity (Email, Sender,
MailThread, MailDomain, Attachment, Label, SyncState, AnalyticsAggregate,
WidgetLayout, UnsubscribeItem) gets a global query filter restricting reads to the
authenticated user. AppDbContext takes an optional ICurrentUser; CurrentUserId is
Guid.Empty for background workers / design-time, which DISABLES the filter so sync
and tooling (which already scope by an explicit userId) are unaffected. On the HTTP
attack surface a forgotten manual `WHERE UserId ==` can no longer leak another
tenant's rows.

Phase 1 confirmed no active IDOR; this is preventive, and prioritised now because the
upcoming automation engine will add many new queries.

Also: moved the Npgsql-only tsvector FTS mapping out of EmailConfiguration into
AppDbContext.OnModelCreating, guarded by Database.IsRelational() (Ignored otherwise),
so non-relational test providers work — honouring the existing Email.SearchVector
comment. Production (Npgsql) model is unchanged; no migration needed.

Adds 3 cross-user tenant-isolation integration tests. All 38 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 00:20:56 +02:00
cesnimda 626a9f8454 fix(security): Phase 4 edge hardening + SSRF egress guard
Backend security fixes from the Phase 1 register / Phase 2 roadmap (PR1 + V-01):

- V-01 SSRF: new SafeHttpGuard validates outbound unsubscribe URLs (scheme allowlist
  + DNS-resolve-and-block private/loopback/link-local/ULA/metadata ranges), wired into
  UnsubscribeService; the "unsubscribe" HttpClient now disables auto-redirect so a
  validated external URL can't 3xx into an internal target. +33 unit tests.
- V-04: session cookie SecurePolicy=Always in non-dev (SameAsRequest in dev).
- V-06: UseExceptionHandler/ProblemDetails in prod; Cleanup/Unsubscribe no longer
  echo ex.Message to clients (logged server-side, generic message returned).
- V-08: ForwardedHeaders trusted only from configurable KnownNetworks (default private
  ranges) + ForwardLimit, instead of trusting any client. New ForwardedHeaders config.
- V-09: returnUrl validated with Url.IsLocalUrl (no open redirect via OAuth flow).
- V-10: SearchService clamps Page/PageSize (<=200); Analytics clamps take/days.
- V-11: baseline security headers (nosniff, X-Frame-Options DENY, Referrer-Policy,
  COOP) + HSTS in prod.
- V-13: /app/info discloses only devMode to anonymous callers unless dev mode is on.
- V-12: API container runs as non-root 'app' user (keys dir pre-owned).
- V-03: Postgres + API ports bound to 127.0.0.1; POSTGRES_PASSWORD now required (no
  weak default fallback).

API compatibility preserved (clamps not rejections; error-body shape changes only on
failure paths). No DB migrations. Build + all 33 unit tests green. V-15 (MailKit
NU1902) persists across versions and the SMTP path is default-off — tracked, not bumped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 00:15:04 +02:00
cesnimda 9ae61432ba feat(ui): F1+F2 — component primitives + rebuilt app shell
F1: shadcn-style primitive set in components/ui (Button, Card, Badge, Input,
Switch, Separator, Skeleton, Tooltip, DropdownMenu, Dialog, Sheet, Tabs, Table,
Toast+useToast, ThemeToggle, PageHeader/StatCard/EmptyState) on Radix + cva.

F2: Layout rebuilt on the new system — sidebar with primary nav (lucide icons)
+ favorites/saved-searches/mailbox/smart-folder sections, sticky topbar with
search, sync status, theme toggle, digest toggle, and an account dropdown. All
prior behavior preserved (auth, sync, collapse/section persistence, "/" search
focus). App wrapped in ToastProvider + TooltipProvider. Build green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 23:51:45 +02:00
cesnimda 1bd8143a87 feat(ui): F0 — Tailwind + design tokens + theme infrastructure
Install Tailwind v3, Radix primitives, cva/tailwind-merge/clsx, lucide-react.
Add HSL design tokens (light + dark) with the Indigo #5b5bf0 accent isolated
to a single --primary token (swappable for a future accent picker), darkMode
'class', anti-flash inline script, useTheme hook, and cn() helper. styles.css
still loads for not-yet-migrated pages. Build green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 23:11:51 +02:00
cesnimda 6af03ea807 docs: switch Privacy Monitor default to XposedOrNot (free, keyless)
Email breach endpoints need no API key (2 req/s, cached), so Privacy Monitor
ships enabled by default. HIBP kept as a swappable key-based alternative;
provider chosen via Privacy:Provider config.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 23:07:43 +02:00
cesnimda be6cbf90d7 docs: full specs for automation engine, sender policy, activity log, privacy monitor, UI overhaul
Clean.Email-parity feature build-out plus a Stripe/Notion-style UI rebuild on
Tailwind + shadcn-style primitives. Locks the hybrid-automation, Gmail-only,
light+dark, incremental-rollout decisions and lays out the backend/frontend
build sequence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 23:04:52 +02:00
cesnimda 64f835f719 feat: AI categorisation fallback, unsubscribe confidence scoring, one-line summaries
Heuristic classifier falls back to AI only when it can't determine a category;
unsubscribe confidence blends method reliability with an AI safety opinion and
surfaces it in the Unsubscribe Manager; emails get an on-demand AI one-line
summary in the detail pane. All AI calls degrade gracefully when AI is disabled
or the provider errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 22:21:10 +02:00
cesnimda 0163165beb feat: notification digests via SMTP
Adds a periodic inbox-summary email per opted-in user. SMTP is
configured via appsettings (Smtp section); the digest is built from
existing analytics (health score, top senders, recommendations) and
sent by a new hourly DigestWorker, mirroring the GmailSyncWorker
pattern. Frequency and send hour are configurable (Digest section).

Backend: IEmailSender (SmtpEmailSender, MailKit), IDigestService,
DigestWorker, new User.DigestEnabled/LastDigestSentUtc fields +
migration, SettingsController for the per-user toggle and a
send-now test endpoint, all scoped to the authenticated UserId.

Frontend: SettingsApi + a bell/no-bell toggle button in the topbar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 22:11:39 +02:00
cesnimda 9bd3799fbc feat: saved searches
Lets users name and pin a search query from the SearchResults page;
saved searches persist to localStorage and show as quick links in a new
sidebar section, similar to Favorites. Clicking re-runs the query.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 22:00:12 +02:00
cesnimda c7ead8caae feat: keyboard shortcuts on Senders email list
Wires the shared useListKeyboardNav hook into SenderEmails so j/k/e/#
work there too, matching FolderView and SearchResults.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 21:58:40 +02:00
cesnimda 7710d49c77 feat: keyboard shortcuts for email navigation
'/' focuses the search bar from anywhere (Layout-level listener).
j/k move focus up/down the email list in FolderView and SearchResults;
e archives the focused email, # trashes it. All shortcuts are ignored
while an input/textarea/select has focus, so typing is never hijacked.

A small hint footer in the bottom-right reminds users of the shortcuts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 21:55:36 +02:00
cesnimda c11d747919 feat: bulk actions for email lists
Select multiple rows (checkbox per row) in FolderView, SearchResults,
and the Senders email panel. A toolbar appears with mark read/unread,
star, archive, and trash, applied to the whole selection via
CleanupService.ExecuteAsync (already scoped to UserId).

Shared via useSelection hook and BulkToolbar component to avoid
duplicating selection state across the three list views.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 21:53:43 +02:00
cesnimda 8c92263cc3 feat: sync status indicator, unsubscribe queue UI, email body preview
SyncStatus widget polls /sync/status and shows a live progress bar in
the topbar while syncing, or last-synced time / error otherwise.

Unsubscribe page reworked into a proper queue: status filter tabs
(All/Pending/Succeeded/Failed), select-all, sorted by volume, status
badges, and a result toast after processing.

GET /email/{id} returns full BodyText; Senders detail view fetches
and renders it instead of just the snippet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 21:50:59 +02:00
cesnimda a24c48179a feat: senders master-detail layout with inline email view
Left panel: scrollable sender list with filter, email count, unread badge.
Right panel: infinite-scroll email list, quick actions, click row to view
detail (subject, meta, snippet, open in Gmail). Back button returns to list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 21:40:01 +02:00
cesnimda 0dfd739430 feat: hide empty smart folders in sidebar
Smart folders with zero matching emails are not rendered once
sidebar counts have loaded. Folders still show while counts are
pending (avoids flash of empty sidebar on load).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 20:45:48 +02:00
cesnimda 21606c5f91 feat: add 14 new smart folder categories (locale-agnostic)
New EmailCategory enum values: Travel, Subscriptions, Parcels,
Recruitment, Events, SecurityAlerts, Healthcare, Education,
NewsMedia, PropertyUtilities, Charity, Government, CryptoInvesting,
FamilySchool.

HeuristicClassifier: locale-agnostic rules using global brand
domains + TLD patterns (.gov.*, .edu, .ac.*) + English subject
keywords — works for international users without relying on
country-specific domains (e.g. gov.uk).

Priority order ensures SecurityAlerts and Government take
precedence over Finance, and Travel/RideSharing/FoodDelivery
fire before Finance to prevent receipt mis-classification.

AnalyticsService: maps all 14 new slugs to category counts.
client.js: folderToRequest() handles all 14 new slugs.
Layout.jsx: SMART_FOLDERS extended with icons and labels.
widgets.jsx: CAT_SLUG updated for dashboard drillthrough.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 20:44:21 +02:00
cesnimda 95dc835651 feat: expanded smart folder category coverage (Gaming, RideSharing, FoodDelivery, SeasonalSales, Wellness, Shopping)
- Added 6 new EmailCategory enum values
- HeuristicClassifier rewritten with domain and subject-keyword rules:
  - RideSharing: Uber, Lyft, Bolt, Free Now, Grab etc.
  - FoodDelivery: DoorDash, Uber Eats, Deliveroo, Just Eat, Grubhub etc.
  - Gaming: Steam, Epic, EA, Xbox, PlayStation, Nintendo, Blizzard, Riot etc.
  - Shopping: Amazon, eBay, Etsy, ASOS, Zalando, Shein etc.
  - SeasonalSales: subject keywords (sale, % off, promo, black friday etc.)
  - Wellness: Strava, Garmin, Peloton, MyFitnessPal, Headspace, Nike etc. + subject hints
  - Priority ordering prevents ride-share/food receipts misclassifying as Finance
- SidebarCounts maps all new slugs to their EmailCategory
- frontend folderToRequest() and CAT_SLUG map all new categories
- Existing emails will pick up new categories on next sync

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 20:24:07 +02:00
cesnimda 2c9b402b08 feat: inline unsubscribe button on email rows
- ✉✕ button appears on rows where the email has a List-Unsubscribe header
- One-click POST / HTTP GET executed automatically; shows ✓ on success
- mailto: targets open the user mail client (we never auto-send email)
- POST /email/{id}/unsubscribe: detects sender, then processes — UserId scoped
- EmailSummaryDto gains IsStarred, HasListUnsubscribe, SupportsOneClick fields
- Both SearchService and CleanupService updated to populate new DTO fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 20:22:28 +02:00
cesnimda 80c2167b89 feat: dashboard drillthrough — widgets navigate to filtered views
- Total Emails stat card -> /app/folder/allmail
- Unread stat card -> /app/folder/unread
- Top Senders rows -> /app/search?q=from:{address}
- Category heatmap labels -> matching folder or search (Finance, Social, Notification, Promotional, Spam, Newsletter, Personal)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 20:20:09 +02:00
cesnimda 2f6d6abdd3 feat: quick actions on email rows (read/unread, star, trash)
- EmailRow shared component with hover action buttons: mark read/unread, star/unstar, trash
- Optimistic UI — local state patches immediately on click; row fades and becomes non-interactive during the API call
- Trashed rows disappear from the current folder view via onRemove callback
- Backend: EmailController single-email endpoints (POST /email/{id}/read|unread|star|unstar|trash|untrash)
- Star/Unstar added to CleanupActionType enum and CleanupService (maps to STARRED Gmail label via BatchModifyAsync)
- UserId scope enforced in ResolveTargetsAsync — a user can only act on their own emails
- action buttons use stopPropagation so clicking them does not open Gmail

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 20:18:44 +02:00
cesnimda 498536451e feat: search bar in topbar with Gmail-style query syntax
- Search form in topbar routes to /app/search?q=...
- SearchResults page with infinite scroll, same row style as FolderView
- Supports Gmail-like operators: from:, is:unread, has:attachment, after:, before:
- SearchApi.query() calls GET /search which runs through the existing GmailQueryParser
- Input sanitised via encodeURIComponent on submit; external links use noopener/noreferrer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 20:15:45 +02:00
cesnimda 4d15a3a8fb feat: click email row opens it in Gmail (noopener/noreferrer)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 20:14:06 +02:00
cesnimda 4b1d1b1b6e feat: infinite scroll for folder view
Replace page buttons with IntersectionObserver sentinel — scrolling near
the bottom automatically fetches and appends the next page of results.
Shows a loading indicator while fetching and a total count footer when
all emails are loaded.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 20:05:17 +02:00
cesnimda 20d51b2c15 feat: folder view — clicking sidebar items shows filtered email list
- New FolderView page at /app/folder/:slug renders a paginated email table
- Sidebar links now route to their respective folder views
- Extended SearchRequestDto with IsInInbox, IsStarred, IsTrashed, GmailLabel, Category, MinSizeBytes
- SearchService applies the new filters; GmailLabel does a label-id lookup (SENT/DRAFT/SPAM)
- Frontend folderToRequest() maps every slug to the correct search payload
- Email rows show sender, subject+snippet, attachment icon, size, date; bold for unread
- Pagination controls for large folders

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 19:59:01 +02:00