Found and fixed multiple critical bugs with similar patterns:
1. routeStatements() used DELETE FROM d1_routes before re-inserting
- Could cause data loss if interrupted or if logic changes
- Now uses DELETE with WHERE clause + INSERT...ON CONFLICT (upsert)
2. saveFragments() used DELETE FROM d1_fragments before re-inserting
- Same pattern as routes, fixed with targeted deletes + upsert
- Added loadFragments() call to determine what to delete
3. adminGroupRename() didn't update fragment groupId
- When renaming a group, fragments were left pointing to old groupId
- Now updates fragments alongside routes, secrets, and invites
All changes follow the same safe pattern:
- Load existing data to identify what needs deletion
- Delete only removed items with WHERE clauses
- Use INSERT...ON CONFLICT DO UPDATE for upserts
- Never use bare DELETE FROM table
Testing: All 339 tests pass.
Add a field filter type reading any payload value by JSONPath with array expansion, 12 comparison operators (eq/ne/contains/startsWith/endsWith/regex/gt/gte/lt/lte/in/exists), a visual AST builder (all/any/not) in the route editor, chip-based multi-value input, a stateless POST /admin/api/test-match dry-run, and named filter fragments stored in D1 (d1_fragments, migration 0010) inlined into route ASTs on insert.