Files
familienarchiv/docs/superpowers/plans/2026-06-07-remove-nlp-search.md
Marcel be7ad1d1fa refactor(search): delete backend NLP search package
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>
2026-06-07 18:41:36 +02:00

23 KiB
Raw Blame History

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 files
  • backend/src/test/java/org/raddatz/familienarchiv/search/ — 6 Java test files
  • frontend/src/routes/search/SmartModeToggle.svelte + .spec.ts
  • frontend/src/routes/search/SmartSearchStatus.svelte + .spec.ts
  • frontend/src/routes/search/InterpretationChipRow.svelte + .spec.ts
  • frontend/src/routes/search/DisambiguationPicker.svelte + .spec.ts
  • frontend/src/routes/search/chip-types.ts
  • frontend/src/routes/documents/theme-chip-removal.ts + .spec.ts
  • infra/observability/grafana/provisioning/dashboards/ollama.json
  • nlp-service/ (entire directory)
  • 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

Modify

  • backend/src/main/java/org/raddatz/familienarchiv/exception/ErrorCode.java — remove 2 enum values
  • backend/src/main/resources/application.yaml — remove nlp + nl-search config blocks
  • backend/src/main/resources/application-dev.yaml — remove nlp config block
  • frontend/src/routes/SearchFilterBar.svelte — remove SmartModeToggle, smartMode prop, smart callbacks
  • frontend/src/routes/SearchFilterBar.svelte.spec.ts — remove smart-mode describe block
  • frontend/src/routes/documents/+page.svelte — remove all NL state, functions, template block
  • frontend/src/lib/shared/errors.ts — remove 2 error codes + their switch cases
  • frontend/messages/de.json — remove 8 smart-search keys
  • frontend/messages/en.json — remove 8 smart-search keys
  • frontend/messages/es.json — remove 8 smart-search keys
  • docker-compose.yml — remove nlp-service block + backend depends_on + env var
  • docker-compose.prod.yml — remove nlp-service block + backend depends_on + env var
  • infra/observability/prometheus/prometheus.yml — remove ollama scrape job
  • CLAUDE.md — remove search package reference + error code entries
  • backend/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 (138142):

    // --- 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 (133138):

  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 (1517):

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 92105) 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 199233 (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 1116, 2327)

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 5170)

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 202318)

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 5657)

Remove from the ErrorCode type union:

	| 'SMART_SEARCH_UNAVAILABLE'
	| 'SMART_SEARCH_RATE_LIMITED'
  • Step 2: Remove switch cases (lines 183186)

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 ~148179, 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 ~206221, 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"