Remove entire backend search domain including: - NlSearchController, NlQueryParserService, NlpClient implementations - Rate limiting, properties, DTOs (NlSearchRequest/Response/NlQueryInterpretation) - All domain logic and tests (5 test files deleted) Backend compiles successfully post-deletion. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
23 KiB
Remove NLP/Smart Search Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Remove the NLP/smart-search feature entirely from the codebase — backend search package, frontend components, i18n keys, infrastructure config, and the nlp-service microservice.
Architecture: Pure deletion + targeted edits. No new code. Each task deletes a self-contained layer, then verifies compilation passes before committing. Order: backend first (most isolated), then frontend, then infrastructure, then docs.
Tech Stack: Spring Boot 4 (Java 21, Maven), SvelteKit 2 / Svelte 5, Docker Compose, Paraglide i18n.
File Map
Delete entirely
backend/src/main/java/org/raddatz/familienarchiv/search/— 14 Java source filesbackend/src/test/java/org/raddatz/familienarchiv/search/— 6 Java test filesfrontend/src/routes/search/SmartModeToggle.svelte+.spec.tsfrontend/src/routes/search/SmartSearchStatus.svelte+.spec.tsfrontend/src/routes/search/InterpretationChipRow.svelte+.spec.tsfrontend/src/routes/search/DisambiguationPicker.svelte+.spec.tsfrontend/src/routes/search/chip-types.tsfrontend/src/routes/documents/theme-chip-removal.ts+.spec.tsinfra/observability/grafana/provisioning/dashboards/ollama.jsonnlp-service/(entire directory)docs/adr/028-nl-search-ollama.mddocs/adr/028-ollama-docker-compose-service.mddocs/adr/034-ollama-production-deployment-and-keep-alive.mddocs/adr/035-rule-based-nlp-service.md
Modify
backend/src/main/java/org/raddatz/familienarchiv/exception/ErrorCode.java— remove 2 enum valuesbackend/src/main/resources/application.yaml— removenlp+nl-searchconfig blocksbackend/src/main/resources/application-dev.yaml— removenlpconfig blockfrontend/src/routes/SearchFilterBar.svelte— remove SmartModeToggle, smartMode prop, smart callbacksfrontend/src/routes/SearchFilterBar.svelte.spec.ts— remove smart-mode describe blockfrontend/src/routes/documents/+page.svelte— remove all NL state, functions, template blockfrontend/src/lib/shared/errors.ts— remove 2 error codes + their switch casesfrontend/messages/de.json— remove 8 smart-search keysfrontend/messages/en.json— remove 8 smart-search keysfrontend/messages/es.json— remove 8 smart-search keysdocker-compose.yml— remove nlp-service block + backend depends_on + env vardocker-compose.prod.yml— remove nlp-service block + backend depends_on + env varinfra/observability/prometheus/prometheus.yml— remove ollama scrape jobCLAUDE.md— remove search package reference + error code entriesbackend/CLAUDE.md— no change needed (search package already absent from structure)frontend/CLAUDE.md— update routes/search/ description
Task 1: Delete backend search package
Files:
-
Delete:
backend/src/main/java/org/raddatz/familienarchiv/search/(14 files) -
Delete:
backend/src/test/java/org/raddatz/familienarchiv/search/(6 files) -
Step 1: Delete all source files
rm -rf backend/src/main/java/org/raddatz/familienarchiv/search
rm -rf backend/src/test/java/org/raddatz/familienarchiv/search
- Step 2: Verify backend compiles
cd backend && . ~/.sdkman/candidates/java/current/bin/../.. && source ~/.sdkman/bin/sdkman-init.sh && ./mvnw compile -q
Expected: BUILD SUCCESS with no errors.
- Step 3: Commit
git add -A
git commit -m "refactor(search): delete backend NLP search package"
Task 2: Remove ErrorCode entries and backend config
Files:
-
Modify:
backend/src/main/java/org/raddatz/familienarchiv/exception/ErrorCode.java:138-142 -
Modify:
backend/src/main/resources/application.yaml:133-138 -
Modify:
backend/src/main/resources/application-dev.yaml:15-17 -
Step 1: Remove NL Search enum values from ErrorCode.java
Remove these lines (138–142):
// --- NL Search ---
/** Ollama is unreachable or timed out. 503 */
SMART_SEARCH_UNAVAILABLE,
/** NL search rate limit exceeded (5 requests per user per minute). 429 */
SMART_SEARCH_RATE_LIMITED,
The block between TAG_MERGE_INVALID_TARGET, and // --- Generic --- becomes empty.
- Step 2: Remove nlp and nl-search config from application.yaml
Remove these lines (133–138):
nlp:
base-url: http://nlp-service:8001
nl-search:
rate-limit:
max-requests-per-minute: 20
- Step 3: Remove nlp config from application-dev.yaml
Remove these lines (15–17):
app:
nlp:
base-url: http://localhost:8001
Note: only remove the nlp: sub-key under app:, preserving any other app: config above it.
- Step 4: Verify backend still compiles
cd backend && source ~/.sdkman/bin/sdkman-init.sh && ./mvnw compile -q
Expected: BUILD SUCCESS.
- Step 5: Commit
git add backend/src/main/java/org/raddatz/familienarchiv/exception/ErrorCode.java \
backend/src/main/resources/application.yaml \
backend/src/main/resources/application-dev.yaml
git commit -m "refactor(search): remove NLP error codes and application config"
Task 3: Delete frontend NL search components and utilities
Files:
-
Delete:
frontend/src/routes/search/SmartModeToggle.svelte+.spec.ts -
Delete:
frontend/src/routes/search/SmartSearchStatus.svelte+.spec.ts -
Delete:
frontend/src/routes/search/InterpretationChipRow.svelte+.spec.ts -
Delete:
frontend/src/routes/search/DisambiguationPicker.svelte+.spec.ts -
Delete:
frontend/src/routes/search/chip-types.ts -
Delete:
frontend/src/routes/documents/theme-chip-removal.ts+.spec.ts -
Step 1: Delete all NL search components, specs, and utilities
rm frontend/src/routes/search/SmartModeToggle.svelte \
frontend/src/routes/search/SmartModeToggle.svelte.spec.ts \
frontend/src/routes/search/SmartSearchStatus.svelte \
frontend/src/routes/search/SmartSearchStatus.svelte.spec.ts \
frontend/src/routes/search/InterpretationChipRow.svelte \
frontend/src/routes/search/InterpretationChipRow.svelte.spec.ts \
frontend/src/routes/search/DisambiguationPicker.svelte \
frontend/src/routes/search/DisambiguationPicker.svelte.spec.ts \
frontend/src/routes/search/chip-types.ts \
frontend/src/routes/documents/theme-chip-removal.ts \
frontend/src/routes/documents/theme-chip-removal.spec.ts
- Step 2: Commit
git add -A
git commit -m "refactor(search): delete frontend NLP search components and utilities"
Task 4: Remove NL search from SearchFilterBar
Files:
-
Modify:
frontend/src/routes/SearchFilterBar.svelte -
Modify:
frontend/src/routes/SearchFilterBar.svelte.spec.ts:199-233 -
Step 1: Rewrite SearchFilterBar.svelte
Replace the entire <script> block with:
<script lang="ts">
import PersonTypeahead from '$lib/person/PersonTypeahead.svelte';
import TagInput from '$lib/tag/TagInput.svelte';
import DateInput from '$lib/shared/primitives/DateInput.svelte';
import SortDropdown from '$lib/shared/primitives/SortDropdown.svelte';
import { slide } from 'svelte/transition';
import { m } from '$lib/paraglide/messages.js';
let {
q = $bindable(''),
from = $bindable(''),
to = $bindable(''),
senderId = $bindable(''),
receiverId = $bindable(''),
tagNames = $bindable<{ name: string; id?: string; color?: string; parentId?: string }[]>([]),
tagQ = $bindable(''),
tagOperator = $bindable<'AND' | 'OR'>('AND'),
undated = $bindable(false),
undatedCount = 0,
sort = $bindable('DATE'),
dir = $bindable('desc'),
showAdvanced = $bindable(false),
initialSenderName = '',
initialReceiverName = '',
navKey = 0,
isLoading = false,
onSearch,
onSearchImmediate,
onfocus,
onblur
}: {
q?: string;
from?: string;
to?: string;
senderId?: string;
receiverId?: string;
tagNames?: { name: string; id?: string; color?: string; parentId?: string }[];
tagQ?: string;
tagOperator?: 'AND' | 'OR';
undated?: boolean;
undatedCount?: number;
sort?: string;
dir?: string;
showAdvanced?: boolean;
initialSenderName?: string;
initialReceiverName?: string;
navKey?: number;
isLoading?: boolean;
onSearch: () => void;
onSearchImmediate?: () => void;
onfocus?: () => void;
onblur?: () => void;
} = $props();
// Plain (non-reactive) flag — not $state, so no reactive assignment inside $effect
let sortDirMounted = false;
$effect(() => {
void sort;
void dir;
if (!sortDirMounted) {
sortDirMounted = true;
return;
}
onSearch();
});
</script>
- Step 2: Update the search input element in the template
Replace the <input type="text" ...> element (lines 92–105) with:
<input
type="text"
bind:value={q}
oninput={onSearch}
onfocus={onfocus}
onblur={onblur}
aria-label={m.docs_search_placeholder()}
placeholder={m.docs_search_placeholder()}
class="block w-full border-line py-2.5 pl-10 pr-20 placeholder-ink-3 shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
/>
- Step 3: Remove the SmartModeToggle component from the template
Delete this line (135):
<SmartModeToggle bind:smartMode={smartMode} onToggle={onModeToggle} />
- Step 4: Remove smart-mode describe block from SearchFilterBar.svelte.spec.ts
Delete lines 199–233 (the entire final describe block):
describe('SearchFilterBar – smart-mode chip lifecycle hooks', () => {
// ...
});
- Step 5: Run the SearchFilterBar tests to verify they pass
cd frontend && source ~/.nvm/nvm.sh && npm run test -- --project=client src/routes/SearchFilterBar.svelte.spec.ts
Expected: all tests pass, no failures.
- Step 6: Commit
git add frontend/src/routes/SearchFilterBar.svelte \
frontend/src/routes/SearchFilterBar.svelte.spec.ts
git commit -m "refactor(search): remove smart mode from SearchFilterBar"
Task 5: Remove NL search from documents/+page.svelte
Files:
- Modify:
frontend/src/routes/documents/+page.svelte
This is the largest edit. Remove all NL search state, derived values, functions, and the NL results template block.
- Step 1: Remove NL search imports (lines 11–16, 23–27)
Remove these import lines:
import SmartSearchStatus from '../search/SmartSearchStatus.svelte';
import InterpretationChipRow from '../search/InterpretationChipRow.svelte';
import type { ChipType } from '../search/chip-types.js';
import { buildThemeRemovalUrl } from './theme-chip-removal.js';
import DisambiguationPicker from '../search/DisambiguationPicker.svelte';
Remove these type aliases:
type NlQueryInterpretation = components['schemas']['NlQueryInterpretation'];
type NlSearchResponse = components['schemas']['NlSearchResponse'];
type DocumentSearchResult = components['schemas']['DocumentSearchResult'];
type PersonHint = components['schemas']['PersonHint'];
type SmartSearchErrorCode = 'SMART_SEARCH_UNAVAILABLE' | 'SMART_SEARCH_RATE_LIMITED';
Also remove the import { csrfFetch } from '$lib/shared/cookies'; line — it is only used by runSmartSearch.
- Step 2: Remove all NL state and derived values (lines 51–70)
Remove these declarations:
// Smart (NL) search — UI-local state, resets on real page navigation (away + back).
let smartMode = $state(false);
let nlSubmitted = $state(false);
let nlLoading = $state(false);
let nlError = $state<SmartSearchErrorCode | null>(null);
let nlInterpretation = $state<NlQueryInterpretation | null>(null);
let nlResult = $state<DocumentSearchResult | null>(null);
const showNlView = $derived(smartMode && nlSubmitted);
const nlHasResults = $derived((nlResult?.items.length ?? 0) > 0);
const ambiguousPersons = $derived(nlInterpretation?.ambiguousPersons ?? []);
const nlIsAmbiguous = $derived(ambiguousPersons.length > 0);
const disambiguationHeading = $derived(
ambiguousPersons.length === 1
? m.search_disambiguation_did_you_mean({ name: ambiguousPersons[0].displayName })
: m.search_disambiguation_heading()
);
const showDisambiguationCue = $derived(ambiguousPersons.length >= 2);
- Step 3: Remove all NL search functions (lines 202–318)
Remove these functions entirely:
-
resetNlState() -
onModeToggle() -
runSmartSearch() -
switchToKeywordMode() -
applyResolvedAndSearch() -
paramsFromInterpretation() -
removeChip() -
selectDisambiguated() -
Step 4: Update SearchFilterBar usage in the template
Replace the SearchFilterBar call with (removing bind:smartMode, onSmartSearch, onModeToggle):
<SearchFilterBar
bind:q={q}
bind:from={from}
bind:to={to}
bind:senderId={senderId}
bind:receiverId={receiverId}
bind:tagNames={tagNames}
bind:showAdvanced={showAdvanced}
bind:sort={sort}
bind:dir={dir}
bind:tagQ={tagQ}
bind:tagOperator={tagOperator}
bind:undated={undated}
undatedCount={data.undatedCount ?? 0}
initialSenderName={initialSenderName}
initialReceiverName={initialReceiverName}
navKey={navKey}
isLoading={navigating.to !== null}
onSearch={handleTextSearch}
onSearchImmediate={handleImmediateSearch}
onfocus={() => (qFocused = true)}
onblur={() => (qFocused = false)}
/>
- Step 5: Remove the NL results template block
Replace the entire {#if showNlView}...{:else}...{/if} block with just the content of the {:else} branch — the <div class="mt-3 mb-4 hidden lg:block"> block through the closing {/if}, unwrapped. The result should be:
<div class="mt-3 mb-4 hidden lg:block">
<TimelineDensityFilter
... (keep as-is)
/>
</div>
<div class="mb-3 flex items-center justify-between gap-4">
... (keep as-is)
</div>
<DocumentList ... (keep as-is) />
<Pagination ... (keep as-is) />
- Step 6: Verify frontend type-checks cleanly for this file
cd frontend && source ~/.nvm/nvm.sh && npm run check 2>&1 | grep "documents/+page.svelte"
Expected: no errors for +page.svelte (pre-existing errors in other files are acceptable).
- Step 7: Commit
git add frontend/src/routes/documents/+page.svelte
git commit -m "refactor(search): remove NLP smart search from documents page"
Task 6: Remove error codes from frontend errors.ts
Files:
-
Modify:
frontend/src/lib/shared/errors.ts -
Step 1: Remove error code union members (lines 56–57)
Remove from the ErrorCode type union:
| 'SMART_SEARCH_UNAVAILABLE'
| 'SMART_SEARCH_RATE_LIMITED'
- Step 2: Remove switch cases (lines 183–186)
Remove from the getErrorMessage() switch:
case 'SMART_SEARCH_UNAVAILABLE':
return m.error_smart_search_unavailable();
case 'SMART_SEARCH_RATE_LIMITED':
return m.error_smart_search_rate_limited();
- Step 3: Commit
git add frontend/src/lib/shared/errors.ts
git commit -m "refactor(search): remove smart search error codes from frontend"
Task 7: Remove i18n messages from all three language files
Files:
- Modify:
frontend/messages/de.json - Modify:
frontend/messages/en.json - Modify:
frontend/messages/es.json
Remove the same set of keys from each file. The keys to remove are:
"error_smart_search_unavailable"
"error_smart_search_rate_limited"
"smart_search_keywords_not_applied"
"search_toggle_smart_label"
"search_toggle_smart_label_suffix"
"search_toggle_keyword_label"
"search_toggle_keyword_label_suffix"
"search_error_unavailable"
"search_error_unavailable_body"
"search_error_rate_limited"
"search_error_rate_limited_body"
"search_empty_nl"
"search_empty_retry_keyword"
"search_disambiguation_did_you_mean"
"search_disambiguation_heading"
Note: not all keys may exist in all three files — remove whichever are present.
- Step 1: Remove smart-search keys from de.json
Open frontend/messages/de.json and delete every key listed above.
- Step 2: Remove smart-search keys from en.json
Open frontend/messages/en.json and delete every key listed above.
- Step 3: Remove smart-search keys from es.json
Open frontend/messages/es.json and delete every key listed above.
- Step 4: Verify JSON is still valid
cd frontend && node -e "
['messages/de.json','messages/en.json','messages/es.json'].forEach(f => {
try { JSON.parse(require('fs').readFileSync(f,'utf8')); console.log(f+': OK'); }
catch(e) { console.error(f+': INVALID - '+e.message); process.exit(1); }
})
"
Expected: all three files print OK.
- Step 5: Commit
git add frontend/messages/de.json frontend/messages/en.json frontend/messages/es.json
git commit -m "refactor(search): remove smart search i18n keys from all language files"
Task 8: Remove nlp-service from docker-compose files
Files:
- Modify:
docker-compose.yml - Modify:
docker-compose.prod.yml
docker-compose.yml
- Step 1: Remove the nlp-service service block
Delete the entire nlp-service: top-level service definition (lines ~148–179, from nlp-service: through its closing line before the next service).
- Step 2: Remove nlp-service from backend depends_on
Find the backend: service's depends_on: list and remove the nlp-service: entry.
- Step 3: Remove APP_NLP_BASE_URL from backend environment
Find the backend: service's environment: section and remove:
APP_NLP_BASE_URL: "http://nlp-service:8001"
docker-compose.prod.yml
- Step 4: Remove the nlp-service service block
Delete the entire nlp-service: top-level service definition (lines ~206–221, from nlp-service: through its closing line).
- Step 5: Remove nlp-service from backend depends_on
Find the backend: service's depends_on: list and remove the nlp-service: entry.
- Step 6: Remove APP_NLP_BASE_URL from backend environment
Find the backend: service's environment: section and remove:
APP_NLP_BASE_URL: http://nlp-service:8001
- Step 7: Validate compose files parse correctly
docker compose -f docker-compose.yml config --quiet && echo "dev: OK"
docker compose -f docker-compose.prod.yml config --quiet && echo "prod: OK"
Expected: both print OK (warnings are acceptable, errors are not).
- Step 8: Commit
git add docker-compose.yml docker-compose.prod.yml
git commit -m "refactor(infra): remove nlp-service from docker-compose files"
Task 9: Remove observability config for Ollama/NLP
Files:
-
Modify:
infra/observability/prometheus/prometheus.yml -
Delete:
infra/observability/grafana/provisioning/dashboards/ollama.json -
Step 1: Remove ollama scrape job from prometheus.yml
Delete these lines from infra/observability/prometheus/prometheus.yml:
- job_name: ollama
static_configs:
- targets: ['ollama:11434']
- Step 2: Delete the Grafana Ollama dashboard
rm infra/observability/grafana/provisioning/dashboards/ollama.json
- Step 3: Commit
git add infra/observability/prometheus/prometheus.yml
git add -A infra/observability/grafana/provisioning/dashboards/
git commit -m "refactor(infra): remove Ollama/NLP observability config"
Task 10: Delete nlp-service directory and ADR docs
Files:
-
Delete:
nlp-service/(entire directory) -
Delete:
docs/adr/028-nl-search-ollama.md -
Delete:
docs/adr/028-ollama-docker-compose-service.md -
Delete:
docs/adr/034-ollama-production-deployment-and-keep-alive.md -
Delete:
docs/adr/035-rule-based-nlp-service.md -
Step 1: Delete the nlp-service microservice
rm -rf nlp-service/
- Step 2: Delete NLP/Ollama ADRs
rm docs/adr/028-nl-search-ollama.md \
docs/adr/028-ollama-docker-compose-service.md \
docs/adr/034-ollama-production-deployment-and-keep-alive.md \
docs/adr/035-rule-based-nlp-service.md
- Step 3: Commit
git add -A
git commit -m "refactor(search): delete nlp-service microservice and Ollama ADRs"
Task 11: Update CLAUDE.md files
Files:
- Modify:
CLAUDE.md - Modify:
frontend/CLAUDE.md
CLAUDE.md (root)
- Step 1: Remove search package from backend package structure table
Delete this row from the Package Structure section:
├── search/ NL search domain — NlSearchController, NlQueryParserService, RestClientOllamaClient, NlSearchRateLimiter
- Step 2: Remove NLP error codes from Error Handling section
In the Error Handling LLM reminder, remove:
SMART_SEARCH_UNAVAILABLE (HTTP 503 — Ollama inference service offline or timed out)SMART_SEARCH_RATE_LIMITED (HTTP 429 — user exceeded 5 NL search requests per minute)
from both the Backend Architecture and Frontend Architecture sections.
frontend/CLAUDE.md
- Step 3: Update routes/search/ description
Replace:
│ ├── search/ # Smart (NL) search sub-components — SmartModeToggle, InterpretationChipRow, SmartSearchStatus, DisambiguationPicker (no +page; consumed by documents/ and SearchFilterBar)
With:
│ ├── search/ # (empty — NL search removed)
Or delete the line entirely if the directory will be empty after removing all component files.
- Step 4: Commit
git add CLAUDE.md frontend/CLAUDE.md
git commit -m "docs(claude): remove NLP search references from CLAUDE.md files"
Task 12: Verify and regenerate API types
- Step 1: Install frontend dependencies if needed
cd frontend && source ~/.nvm/nvm.sh && npm install
- Step 2: Run frontend vitest to verify no regressions
cd frontend && source ~/.nvm/nvm.sh && npm run test -- --reporter=verbose 2>&1 | tail -30
Expected: all tests pass. The deleted spec files are gone and no longer run.
- Step 3: Start backend to regenerate API types (requires Docker stack)
If backend is running locally:
cd frontend && source ~/.nvm/nvm.sh && npm run generate:api
If not running, skip — the generated types remain valid until the next backend change (no NLP types are referenced after this plan).
- Step 4: Run a final backend compile
cd backend && source ~/.sdkman/bin/sdkman-init.sh && ./mvnw compile -q
Expected: BUILD SUCCESS.
- Step 5: Commit regenerated API types (if generate:api was run)
git add frontend/src/lib/generated/
git commit -m "chore(api): regenerate API types after NLP search removal"
Task 13: Check and clean the empty search/ directory
- Step 1: Check if routes/search/ is now empty
ls frontend/src/routes/search/
If only chip-types.ts was in there (already deleted in Task 3) and all Svelte files are gone, the directory should be empty.
- Step 2: Delete the empty directory
rmdir frontend/src/routes/search/ 2>/dev/null && echo "deleted" || echo "not empty — check contents"
If not empty, list what remains and delete the stray files.
- Step 3: Commit
git add -A
git commit -m "refactor(search): remove empty search/ route directory"