Files
X-Financial/docs/superpowers/plans/2026-06-23-code-deduplication-refactor.md
2026-06-23 11:21:18 +08:00

17 KiB

X-Financial Duplicate Code Refactor Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans for multi-task execution. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Reduce duplicated business logic, renderer helpers, protocol constants, and test fixtures without changing existing behavior.

Architecture: Start with low-risk pure helpers, then move toward business contract consolidation. Each round must add or preserve regression tests before production code changes, and backend validation must run inside x-financial-main.

Tech Stack: Vue 3, Vite, Node test runner, FastAPI, SQLAlchemy, pytest, Docker Compose.


Scope

This document records the duplicate-code audit from 2026-06-23 and defines a staged cleanup path. The first implementation slice is intentionally small: extract shared frontend conversation rendering helpers used by markdown.js and aiConversationHtmlRenderer.js.

The following areas are in scope:

  • Frontend AI markdown / HTML trusted block normalization.
  • Frontend reimbursement review panel model duplication.
  • Workbench AI composer / attachment strip duplication.
  • Backend application gate, fact extraction, amount/date/location parsing.
  • Backend platform risk context helper duplication.
  • Cross-layer status, expense type, document type, and risk-level taxonomy drift.
  • Test helper duplication for DB sessions, FastAPI client overrides, and OCR fakes.

The following areas are out of scope for the first implementation slice:

  • Changing application submission behavior.
  • Changing reimbursement association decision flow.
  • Changing API response shapes.
  • Editing unrelated notification top bar changes already present in the worktree.

Findings

P0: Frontend Trusted HTML And Conversation Text Helpers

web/src/utils/markdown.js and web/src/utils/aiConversationHtmlRenderer.js both implement:

  • ALLOWED_COLON_HEADING_TITLES
  • BUSINESS_FIELD_LABELS
  • TRUSTED_HTML_ALLOWED_TAGS
  • TRUSTED_HTML_ALLOWED_ATTRS
  • splitColonHeadingLine
  • normalizeBusinessFieldLine
  • hasOnlyTrustedHtmlTags
  • sanitizeTrustedHtmlBlock
  • extractTrustedHtmlBlocks
  • restoreTrustedHtmlBlocks

Plan:

  • Create web/src/utils/conversationTrustedHtml.js.
  • Move trusted HTML sanitizing and business-line normalization into the helper.
  • Keep renderer-specific output differences in each renderer.
  • Verify both markdown and AI conversation renderers still preserve valid trusted document cards and reject unsafe trusted HTML.

Expected benefit:

  • One XSS whitelist.
  • One business field normalization rule.
  • Less drift between AI workbench and reimbursement assistant rendering.

P0: Reimbursement Review Panel Model Duplication

web/src/views/scripts/travelReimbursementCreateReviewModel.js and web/src/views/scripts/travelReimbursementReviewPanelModel.js duplicate review scope, fact cards, risk item mapping, risk conversation text, and message cleanup.

Plan:

  • Choose travelReimbursementReviewPanelModel.js as the shared model.
  • Convert create-view imports to the shared model, or make the create-view module a thin compatibility re-export.
  • Add behavior tests for exported review helpers before deleting duplicate code.

Expected benefit:

  • One risk item mapping.
  • One review fact card model.
  • Lower risk when changing reimbursement review copy or drawer behavior.

P0: Workbench AI Composer And File Strip Template Duplication

web/src/components/business/PersonalWorkbenchAiMode.template.html keeps two near-identical composer forms and two near-identical selected-file strips for the welcome and inline conversation states. web/src/components/business/workbench-ai/WorkbenchAiComposer.vue and web/src/components/business/workbench-ai/WorkbenchAiFileStrip.vue already exist, but the main template still duplicates the markup.

Plan:

  • Reuse WorkbenchAiComposer.vue for both welcome and inline composer surfaces.
  • Reuse WorkbenchAiFileStrip.vue for both welcome and inline selected-file strips.
  • Keep the parent runtime API stable by passing a proxied runtime object into shared components.
  • Preserve OCR state display in the shared file-strip component.
  • Keep input focus behavior by exposing an explicit assistant input ref setter.

Expected benefit:

  • One composer markup surface.
  • One selected-file/OCR badge markup surface.
  • Lower maintenance cost when upload, date picker, lock state, or send-button behavior changes.

P1: Application Gate And Fact Extraction

Backend application flow repeats checks across user_agent_application.py, steward_planner.py, orchestrator.py, ontology_detection.py, and ontology_extraction.py.

Plan:

  • Extract application intent / context gate helpers.
  • Extract application fact resolver for date, location, reason, amount, transport, and expense type.
  • Route UserAgent, StewardPlanner, and Orchestrator through the same helpers.
  • Preserve existing application vs reimbursement stage boundaries.

Expected benefit:

  • Fewer mismatches between button actions and text-input actions.
  • Less chance of direct submit re-entering slow /orchestrator/run paths.
  • More consistent missing-field prompts.

P1: Backend Parsing And Risk Context Utilities

Several backend modules repeat city extraction, document field lookup, item-id dedupe, Decimal conversion, endpoint normalization, and JSON error parsing.

Plan:

  • Extract platform risk context helpers for item-id and document field utilities.
  • Reuse existing amount utilities before adding new regex parsing.
  • Share model connectivity URL/header/error helpers between RAG runtime and connectivity checks.
  • Cache sorted travel-policy city names per policy snapshot.

Expected benefit:

  • Less CPU churn in repeated risk/OCR loops.
  • Fewer provider connectivity behavior differences.
  • Easier review of platform-risk regressions.

P1: Cross-Layer Taxonomy And Protocol Constants

Status labels, expense types, document types, risk levels, API paths, and snake_case/camelCase mappings are repeated across backend schemas, frontend services, and tests.

Plan:

  • Establish read-only contract baseline from OpenAPI export.
  • Export status / approval-stage registry to frontend constants.
  • Consolidate expense type, document type, and risk-level taxonomy.
  • Move high-churn API path and payload builders into shared frontend test helpers.

Expected benefit:

  • Fewer display inconsistencies.
  • Easier API evolution.
  • Less brittle source-string tests.

P2: Test Fixture Duplication

server/tests repeatedly defines build_session, build_session_factory, override_db, build_client, and OCR fake functions.

Plan:

  • Add backend test fixtures in server/tests/conftest.py.
  • Move OCR fake builders into a small test helper.
  • Migrate tests in batches, one behavior area at a time.

Expected benefit:

  • Less boilerplate in large test files.
  • Easier targeted regression coverage before service refactors.

First Slice Execution Plan

Task 1: Lock Shared Renderer Helper Behavior

Files:

  • Create: web/tests/conversation-trusted-html.test.mjs
  • Create: web/src/utils/conversationTrustedHtml.js
  • Modify: web/src/utils/markdown.js
  • Modify: web/src/utils/aiConversationHtmlRenderer.js

Steps:

  • Add a failing Node test that imports conversationTrustedHtml.js.
  • Assert valid trusted document-card HTML is preserved through placeholder extraction and restore.
  • Assert unsafe tags, event handlers, and non-document hrefs are rejected.
  • Assert colon headings and business field lines normalize outside fenced code blocks.
  • Run the new test and confirm it fails because the helper does not exist.
  • Implement the helper with pure functions only.
  • Refactor both renderers to use the helper.
  • Run targeted renderer tests.
  • Run npm --prefix web run build.

Validation commands:

node --test web/tests/conversation-trusted-html.test.mjs
node --test web/tests/ai-conversation-html-renderer.test.mjs web/tests/travel-reimbursement-review-drawer-switch.test.mjs
npm --prefix web run build

Third Slice Execution Plan

Task 3: Reuse Workbench AI Composer Components

Files:

  • Create: web/tests/workbench-ai-composer-components.test.mjs
  • Modify: web/src/components/business/PersonalWorkbenchAiMode.vue
  • Modify: web/src/components/business/PersonalWorkbenchAiMode.template.html
  • Modify: web/src/components/business/workbench-ai/WorkbenchAiFileStrip.vue
  • Modify: web/src/composables/workbenchAiMode/usePersonalWorkbenchAiMode.js

Steps:

  • Add a failing Node test that expects the main template to use WorkbenchAiComposer and WorkbenchAiFileStrip.
  • Assert the shared file strip preserves OCR state badges.
  • Assert the runtime exposes an input ref setter for the shared composer.
  • Run the new test and confirm it fails on the duplicated template.
  • Pass a proxied runtime object from PersonalWorkbenchAiMode.vue into shared components.
  • Replace duplicate composer and file-strip markup in the external template.
  • Add OCR badge markup to WorkbenchAiFileStrip.vue.
  • Run targeted workbench AI tests.
  • Run npm --prefix web run build.

Validation commands:

node --test web/tests/workbench-ai-composer-components.test.mjs
node --test web/tests/workbench-ai-composer-components.test.mjs web/tests/workbench-ai-mode-switch.test.mjs web/tests/workbench-ai-mode-expense-scene-action.test.mjs
npm --prefix web run build

Remaining Execution Plan

The remaining work is intentionally split into bounded slices. Each slice extracts shared code without changing user-facing flow, API response shape, or submission semantics.

Task 4: Frontend Application Gate Helpers

Files:

  • Create: web/src/composables/workbenchAiMode/workbenchAiApplicationGateModel.js
  • Create: web/tests/workbench-ai-application-gate-model.test.mjs
  • Modify: web/src/composables/workbenchAiMode/usePersonalWorkbenchAiMode.js
  • Modify: web/src/composables/workbenchAiMode/useWorkbenchAiApplicationPreviewFlow.js

Steps:

  • Add a failing Node test for reimbursement creation intent, submit/save text action resolution, and orphan preview detection.
  • Move pure gate predicates into workbenchAiApplicationGateModel.js.
  • Replace inline copies in personal workbench and application-preview flow.
  • Run targeted workbench AI application tests.

Validation commands:

node --test web/tests/workbench-ai-application-gate-model.test.mjs
node --test web/tests/workbench-ai-application-gate-model.test.mjs web/tests/workbench-ai-mode-switch.test.mjs web/tests/workbench-ai-action-router.test.mjs

Task 5: Backend Application Fact Resolver

Files:

  • Create: server/src/app/services/application_fact_resolver.py
  • Create: server/tests/test_application_fact_resolver.py
  • Modify: server/src/app/services/steward_planner.py

Steps:

  • Add failing pytest coverage for time, location, reason, transport, and task-type inference.
  • Extract pure resolver helpers that preserve existing planner output.
  • Route StewardPlannerService extraction wrappers through the resolver.
  • Run targeted planner/fact tests inside the active app container.

Validation commands:

docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_application_fact_resolver.py server/tests/test_steward_planner.py

Task 6: Backend Platform Risk Context Utilities

Files:

  • Modify: server/src/app/services/expense_claim_platform_context_tools.py
  • Create: server/tests/test_expense_claim_platform_context_tools.py
  • Modify: server/src/app/services/expense_claim_platform_route_risk.py
  • Modify: server/src/app/services/expense_claim_platform_risk.py

Steps:

  • Add failing pytest coverage for context city extraction, item-id dedupe, and text-value dedupe helpers.
  • Add pure helper functions in expense_claim_platform_context_tools.py.
  • Route route-risk and platform-risk consumers through the shared helpers.
  • Run targeted platform-risk tests inside the active app container.

Validation commands:

docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_expense_claim_platform_context_tools.py server/tests/test_expense_claim_platform_risk_stage.py

Task 7: Frontend Protocol Constants And Test Helpers

Files:

  • Create: web/src/constants/documentProtocol.js
  • Create: web/tests/helpers/sourceSurface.mjs
  • Create: web/tests/document-protocol-constants.test.mjs
  • Modify: web/src/composables/workbenchAiMode/workbenchAiApplicationPreviewModel.js
  • Migrate one high-churn source-surface test to the helper.

Steps:

  • Add failing Node tests for status labels, document type constants, and reusable source-surface loading.
  • Move duplicated status labels into documentProtocol.js.
  • Reuse constants in application-preview model and request/document-center model code.
  • Migrate one source-surface test helper to reduce brittle boilerplate.

Validation commands:

node --test web/tests/document-protocol-constants.test.mjs web/tests/workbench-ai-mode-switch.test.mjs

Task 8: Backend Test Fixture Consolidation

Files:

  • Create: server/src/app/test_helpers/db.py
  • Create: server/src/app/test_helpers/__init__.py
  • Create: server/tests/test_db_test_helpers.py
  • Modify: server/tests/test_expense_claim_platform_risk_stage.py

Steps:

  • Identify an existing small test file with duplicated session/client/OCR helpers.
  • Add shared DB helper while preserving its current behavior.
  • Migrate one test file only.
  • Run the migrated test plus adjacent coverage inside the active app container.

Validation commands:

docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_db_test_helpers.py server/tests/test_expense_claim_platform_risk_stage.py

Task 9: Steward Planner Module Split

Files:

  • Create: server/src/app/services/steward_planner_shared.py
  • Create: server/src/app/services/steward_planner_fallback.py
  • Create: server/src/app/services/steward_planner_extraction.py
  • Modify: server/src/app/services/steward_planner.py

Steps:

  • Move shared constants and PlannedTaskDraft into a shared planner module.
  • Move off-topic, pending-flow, and rule-fallback planning into steward_planner_fallback.py.
  • Move task extraction, ontology normalization, attachment grouping, and summary helpers into steward_planner_extraction.py.
  • Keep steward_planner.py as the service orchestration entrypoint.
  • Run planner/fact resolver tests inside the active app container.

Validation commands:

docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q server/tests/test_application_fact_resolver.py server/tests/test_steward_planner.py

Task 10: App Shell Dynamic Business Chunk Loading

Files:

  • Create: web/src/views/scripts/appShellAsyncViews.js
  • Create: web/src/components/shared/AppViewLoadingState.vue
  • Create: web/src/components/shared/AppModalLoadingState.vue
  • Modify: web/src/views/AppShellRouteView.vue
  • Modify: web/src/components/layout/SidebarRail.vue
  • Modify: web/src/components/layout/AiSidebarRail.vue
  • Modify: web/tests/app-shell-route-loading.test.mjs
  • Modify: web/tests/ai-sidebar-rail-mode.test.mjs

Steps:

  • Keep top-level shell routes eager so login/setup/app layout does not blank during route navigation.
  • Move heavy business views behind defineAsyncComponent loaders.
  • Add an in-workarea loading state for slow business chunks.
  • Add a modal loading state for the smart reimbursement assistant chunk.
  • Preload likely next views on sidebar hover/focus and during browser idle time.
  • Preserve existing Vite manual vendor chunks, including vendor-echarts.
  • Run targeted frontend tests and production build.

Validation commands:

node --test web/tests/app-shell-route-loading.test.mjs web/tests/ai-sidebar-rail-mode.test.mjs web/tests/sidebar-document-unread-dot.test.mjs web/tests/workbench-ai-mode-switch.test.mjs web/tests/workbench-ai-reimbursement-association-gate.test.mjs web/tests/travel-reimbursement-review-drawer-switch.test.mjs web/tests/documents-center-status-filter.test.mjs
npm --prefix web run build

Result:

  • App shell entry chunk after split: index-vWyUfHfm.js 223.75 kB, gzip 74.11 kB.
  • Large vendor-echarts chunk remains isolated at 598.67 kB, gzip 204.84 kB; it is no longer part of the app-shell entry chunk.
  • Build still reports the existing Rollup #__PURE__ annotation warnings from Element Plus / VueUse.

Guardrails

  • Do not touch unrelated dirty files.
  • Do not change renderer output intentionally in this slice.
  • Do not move backend logic until frontend helper extraction is green.
  • Backend tests must run through Docker:
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv x-financial-main /tmp/x-financial-server-venv/bin/pytest -q <path>