Blind Flange Calculator Microarchitecture Migration Plan
1. Purpose
This document defines the technical plan for migrating BlindFlangeCalculator from a Docusaurus-bundled React component into a separately maintained Vite application that is published into the host site as a static utility app.
Target source module:
src/components/tools/BlindFlangeCalculator
Target published app path:
static/utility-apps/blind-flange-calculator/app.html
The migration should follow the existing utility-shell pattern already used by standalone utilities such as dxf-editor, where the Docusaurus page renders the common site shell and loads the utility app inside the tool frame.
2. Current State
2.1 Host Site Stack
The root site is a Docusaurus application named cadautoscript-site. It already uses React, TypeScript, Tailwind-related tooling, Vercel analytics, Supabase, Three.js, Replicad/OpenCascade, Monaco, Zustand, jspdf, lucide-react, and other browser utility dependencies. The package manager is pnpm@10.33.0, and the Node engine is 22.x.
2.2 Current Blind Flange Integration
The current page imports the calculator directly into the Docusaurus bundle:
import UtilityShellPage from '@site/src/components/Utilities/UtilityShellPage';
import BlindFlangeCalculator from '@site/src/components/tools/BlindFlangeCalculator';
import {utilityPageConfigs} from '@site/src/data/utilityShellPages';
export default function BlindFlangeCalculatorPage() {
const config = utilityPageConfigs['blind-flange-calculator'];
if (!config) {
throw new Error('Utility page configuration missing for slug "blind-flange-calculator"');
}
return <UtilityShellPage {...config} tool={<BlindFlangeCalculator />} />;
}
This means the tool is compiled together with the documentation site and is tightly coupled to Docusaurus aliases, theme build settings, and site dependency resolution.
2.3 Existing Standalone Utility Pattern
UtilityShellPage already supports two rendering modes:
- If a
toolReact node is passed, it renders the tool directly. - If no
toolis passed, it loads a static utility app through an iframe.
The default iframe path is:
appPath ?? `/utility-apps/${slug}/app.html`
The dxf-editor page uses the generic standalone utility wrapper:
import {createUtilityPage} from '@site/src/components/Utilities/createUtilityPage';
export default createUtilityPage('dxf-editor');
This is the desired integration model for blind-flange-calculator after migration.
3. Migration Goal
Move BlindFlangeCalculator to a separately buildable Vite micro-application while preserving the existing public route:
/utilities/blind-flange-calculator/
The Docusaurus site remains responsible for:
- route ownership
- SEO metadata
- utility shell layout
- auth/access gate
- reactions/comments
- fullscreen shell controls
- utility catalog data
The Vite app becomes responsible for:
- calculator UI
- calculator state
- engineering domain logic
- export actions
- CAD/STEP generation improvements
- local persistence for tool-specific settings, if needed
- future internal architecture evolution independent of the Docusaurus bundle
4. Architectural Direction
4.1 Recommended Architecture
Use a repository-level monorepo-style structure with a standalone Vite app inside the existing repository first. This keeps deployment simple and avoids introducing cross-repository automation too early.
Recommended repository layout:
apps/
blind-flange-calculator/
index.html
package.json
tsconfig.json
vite.config.ts
src/
main.tsx
App.tsx
styles.css
components/
domain/
data/
export/
cad/
workers/
state/
shared/
static/
utility-apps/
blind-flange-calculator/
app.html
assets/
A later phase can split apps/blind-flange-calculator into a separate repository and publish the built artifact back into this site repository.
4.2 Why Start Inside the Same Repository
Starting inside the same repository reduces risk because:
- current calculator code can be moved without package publishing overhead
- Docusaurus integration can be changed in one PR
- build output can be validated against the existing static utility pattern
- root dependencies and versions can be reused or mirrored
- future split into a dedicated repository remains possible once the app boundary is stable
4.3 Later Separate Repository Model
After the Vite app is stable, move it to a separate repository, for example:
biosxxx/blind-flange-calculator-app
That repository should publish a versioned static artifact containing:
app.html
assets/*
manifest.json
checksums.json
The host site can then consume it through one of these approaches:
- GitHub Actions artifact download
- Git submodule
- Git subtree
- package tarball from GitHub Packages
- release asset sync script
Recommended long-term approach: GitHub release asset sync, because it keeps the host repository clean and avoids submodule friction.
5. Target Runtime Integration
5.1 Docusaurus Page
Replace the direct component page with the generic utility page:
import {createUtilityPage} from '@site/src/components/Utilities/createUtilityPage';
export default createUtilityPage('blind-flange-calculator');
This matches the standalone utility pattern used by dxf-editor.
5.2 Utility Config
Keep the existing blind-flange-calculator entry in src/data/utilityShellPages.tsx, but optionally make the app path explicit:
'blind-flange-calculator': {
slug: 'blind-flange-calculator',
title: 'Blind Flange Calculator',
subtitle: 'Web utility - EN 13445-3 blind flange sizing',
description: 'Auto-select EN 1092-1 PN class, calculate blind flange thickness, and estimate weight from DN and pressure.',
about: 'Enter DN, operating and test pressure, temperature, material, and corrosion allowance. The calculator selects the nearest PN class from EN 1092-1, computes minimum thickness per EN 13445-3, and recommends a standard plate thickness with a weight estimate.',
tags: ['EN 13445-3', 'EN 1092-1', 'Flanges'],
note: 'Runs entirely in the browser. Validate final flange sizing, bolt loads, and gasket selection against project specs.',
features: [
'Automatic PN selection from operating pressure',
'Minimum and recommended thickness outputs',
'Bolt circle data and weight estimate',
],
scriptType: 'module',
appPath: '/utility-apps/blind-flange-calculator/app.html',
}
5.3 Vite Build Output
Configure Vite so the generated HTML file is named app.html and all assets use relative paths.
Required Vite settings:
import {defineConfig} from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
base: './',
build: {
outDir: '../../static/utility-apps/blind-flange-calculator',
emptyOutDir: true,
assetsDir: 'assets',
rollupOptions: {
input: 'index.html',
output: {
entryFileNames: 'assets/[name]-[hash].js',
chunkFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash][extname]',
},
},
},
});
After build, rename or emit index.html as app.html. Prefer a small postbuild script so Vite remains conventional:
{
"scripts": {
"build": "vite build && node scripts/rename-html.mjs"
}
}
6. Application Internal Architecture
6.1 Design Principle
The migration must not simply copy the existing component tree into Vite. It should create a maintainable app boundary with clear separation between UI, domain calculations, data, exports, workers, and future CAD features.
6.2 Recommended App Structure
apps/blind-flange-calculator/src/
main.tsx
App.tsx
components/
layout/
ToolHeader.tsx
Panel.tsx
Section.tsx
input/
InputForm.tsx
GeometryModeSelector.tsx
PressureInputs.tsx
MaterialInputs.tsx
FastenerInputs.tsx
results/
ResultsPanel.tsx
ThicknessSummary.tsx
BoltSummary.tsx
ValidationMessages.tsx
export/
ExportActions.tsx
PdfExportButton.tsx
StepExportButton.tsx
domain/
calculations/
calculateBlindFlange.ts
hydrotest.ts
customSizing.ts
manualCheck.ts
standards/
en1092.ts
en13445.ts
materials.ts
fasteners.ts
gaskets.ts
validation/
inputValidation.ts
geometryValidation.ts
types/
blindFlangeTypes.ts
calculationTypes.ts
state/
useBlindFlangeStore.ts
selectors.ts
defaults.ts
persistence.ts
export/
pdf/
text/
download.ts
fileNames.ts
cad/
geometry/
computeBlindFlangeCadGeometry.ts
buildBoltHolePattern.ts
validation.ts
workers/
cadWorker.ts
cadWorkerClient.ts
cadWorkerProtocol.ts
hooks/
useStepExport.ts
shared/
numberFormat.ts
units.ts
errors.ts
6.3 State Management
Use zustand, already present in the site dependencies, for app-level state instead of passing many props through the entire tree.
Recommended store slices:
inputSlice: DN, pressures, temperature, material, corrosion allowancegeometrySlice: standard/custom mode, custom dimensions, selected design configfastenerSlice: standard, type, grade, friction, tightening methodresultSlice: calculated result, custom result, manual check resultexportSlice: PDF/STEP generation stateuiSlice: expanded panels, warnings, selected tabs
Persist only user preferences and non-sensitive calculator defaults. Do not persist generated file buffers.
6.4 Domain Layer Rules
The domain layer must remain framework-independent:
- no React imports
- no DOM access
- no Docusaurus aliases
- no direct file download side effects
- deterministic pure functions where possible
This allows future unit tests, CLI checks, and possible package extraction.
6.5 UI Layer Rules
The UI layer should consume domain selectors and actions. It should not contain engineering formulas directly.
Bad:
const requiredThickness = Math.sqrt(...formula...);
Good:
const result = useBlindFlangeResult();
7. Dependency Strategy
7.1 Reuse Existing Technologies
Use technologies already present in the site where practical:
- React 19
- TypeScript
- Zustand
- Lucide React
- jsPDF
- Replicad / OpenCascade for future STEP generation
- Three.js only if a 3D preview is later added
- existing CSS/Tailwind-compatible utility classes where possible
7.2 Vite-Specific Dependencies
Add only the minimal Vite app dependencies:
{
"dependencies": {
"@vitejs/plugin-react": "latest",
"vite": "latest"
}
}
Use workspace/shared versions where possible. Avoid adding a second UI framework.
7.3 Styling Strategy
Do not depend on Docusaurus theme CSS inside the Vite app.
Use one of these approaches:
- Local CSS modules or plain CSS files copied with the app
- Tailwind build inside the Vite app, if the app needs utility-first styling
- A small shared design-token CSS file copied from the host site
Recommended for the first migration: local CSS with CSS variables matching the current dark engineering UI.
8. Build and Publication Pipeline
8.1 Root Scripts
Add scripts to the root package.json:
{
"scripts": {
"dev:blind-flange": "pnpm --dir apps/blind-flange-calculator dev",
"build:blind-flange": "pnpm --dir apps/blind-flange-calculator build",
"typecheck:blind-flange": "pnpm --dir apps/blind-flange-calculator typecheck",
"sync:blind-flange": "node scripts/sync-blind-flange-calculator.js"
}
}
8.2 Local Development
Use two modes:
- Standalone app development:
pnpm dev:blind-flange
- Host integration check:
pnpm build:blind-flange
pnpm start
Then open:
/utilities/blind-flange-calculator/
8.3 CI Requirements
CI should run:
pnpm install --frozen-lockfile
pnpm typecheck:blind-flange
pnpm build:blind-flange
pnpm typecheck
pnpm build
The host site build must fail if the static app output is missing.
8.4 Artifact Manifest
Generate a small manifest during Vite build:
{
"name": "blind-flange-calculator",
"version": "0.1.0",
"buildTime": "2026-05-04T00:00:00.000Z",
"entry": "app.html",
"assets": ["assets/index-xxxxx.js", "assets/index-yyyyy.css"]
}
The host site can later use this for diagnostics and release verification.
9. Cross-App Communication
9.1 MVP
No complex host/app communication is required for the first migration. The iframe app should run independently.
9.2 Recommended Future Protocol
If the host needs to control theme, fullscreen state, analytics, or auth context, use postMessage with a typed protocol.
Example:
export type HostToUtilityMessage =
| {type: 'theme'; value: 'light' | 'dark'}
| {type: 'fullscreen'; active: boolean}
| {type: 'utility-shell-ready'};
export type UtilityToHostMessage =
| {type: 'utility-ready'; slug: 'blind-flange-calculator'}
| {type: 'utility-error'; message: string}
| {type: 'utility-analytics-event'; name: string; payload?: Record<string, unknown>};
Keep this optional until a real need appears.
10. STEP/CAD Roadmap Alignment
There is already a separate technical specification for adding 3D STEP generation to BlindFlangeCalculator. The Vite migration should prepare for that work by reserving a dedicated cad/ subsystem.
Recommended CAD architecture inside the Vite app:
cad/
geometry/
computeBlindFlangeCadGeometry.ts
buildBlindFlangeSolid.ts
buildBoltHolePattern.ts
validation.ts
workers/
cadWorker.ts
cadWorkerClient.ts
cadWorkerProtocol.ts
hooks/
useStepExport.ts
CAD/STEP generation must run in a Web Worker so the calculator UI remains responsive.
11. Implementation Plan
Phase 1 — Prepare App Boundary
Tasks:
- create
apps/blind-flange-calculator - add Vite, React, TypeScript configuration
- create
main.tsxandApp.tsx - move existing calculator files into the Vite app structure
- remove Docusaurus-specific aliases from moved code
- replace
@siteimports with relative or package-local imports - copy or recreate required CSS locally
Deliverable:
- calculator runs with
pnpm dev:blind-flange
Phase 2 — Reorganize Internal Code
Tasks:
- split UI components from engineering domain logic
- move standards data into
domain/standards - move calculations into
domain/calculations - move export/download helpers into
export/ - introduce Zustand store for app state
- keep existing calculation behavior unchanged
Deliverable:
- calculator behavior matches current production behavior, but internal structure is cleaner
Phase 3 — Static Build Publication
Tasks:
- configure Vite build output to
static/utility-apps/blind-flange-calculator - ensure generated HTML is available as
app.html - ensure assets are loaded through relative paths
- add artifact manifest
- add root build scripts
Deliverable:
static/utility-apps/blind-flange-calculator/app.htmlis produced by the Vite build
Phase 4 — Host Shell Integration
Tasks:
- change
src/pages/utilities/blind-flange-calculator.tsxto usecreateUtilityPage('blind-flange-calculator') - optionally set explicit
appPathinutilityShellPages.tsx - verify auth gate still works through
UtilityShellPage - verify fullscreen and info-panel controls still work
- verify reactions/comments remain on the host page
Deliverable:
/utilities/blind-flange-calculator/loads the Vite app inside the existing utility shell
Phase 5 — Validation and Regression Testing
Tasks:
- compare current and migrated outputs for representative inputs
- test standard geometry mode
- test custom geometry mode
- test manual check/custom sizing paths
- test PDF/export actions
- test mobile layout inside iframe
- test fullscreen mode
- test production Docusaurus build
Deliverable:
- migration accepted with no functional regression
Phase 6 — Separate Repository Publication
Tasks:
- create a dedicated repository for the Vite app
- move app source from
apps/blind-flange-calculator - configure GitHub Actions build
- publish release artifact with
app.html, assets, and manifest - add host-site sync script to download and place artifact under
static/utility-apps/blind-flange-calculator - add checksum verification
Deliverable:
- standalone repository publishes versioned app builds consumed by the site repository
12. Detailed Acceptance Criteria
12.1 Functional
- existing URL remains
/utilities/blind-flange-calculator/ - calculator loads inside the standard utility shell
- all current inputs remain available
- automatic PN selection still works
- hydrotest pressure logic still works
- standard/custom geometry modes still work
- export actions still work
- mobile and fullscreen shell modes remain usable
12.2 Technical
- Docusaurus no longer imports
src/components/tools/BlindFlangeCalculator - standalone app builds through Vite
- static output is placed under
static/utility-apps/blind-flange-calculator - generated app uses relative asset paths
- domain calculations are framework-independent
- app does not depend on Docusaurus runtime aliases
- future CAD worker code has a reserved architecture path
12.3 Quality
- TypeScript passes for both host and utility app
- production Docusaurus build passes
- no duplicated engineering formulas between host and app
- no hidden runtime dependency on root Docusaurus CSS
- errors inside the iframe app are readable to the user
13. Risks and Mitigations
Risk: Duplicated dependency versions
The standalone app can accidentally drift from the host dependency versions.
Mitigation:
- keep it inside the same repository first
- use pnpm workspace version alignment
- document allowed dependency additions
Risk: Broken asset paths after static publication
Vite defaults may emit absolute paths that fail under /utility-apps/....
Mitigation:
- set
base: './' - test built
app.htmlthrough Docusaurus locally
Risk: Losing shell features
Direct component rendering currently avoids iframe boundaries. After migration, shell controls must still work.
Mitigation:
- keep shell-owned features in
UtilityShellPage - avoid moving comments/reactions/auth into the app
- use the existing iframe fallback path
Risk: Over-coupled app source
Simply copying the old folder can preserve tight coupling and make future improvements difficult.
Mitigation:
- reorganize into
domain,state,export,cad, andcomponents - enforce no React imports in domain modules
Risk: Large CAD/WASM payload later
Future STEP generation may increase bundle size.
Mitigation:
- lazy-load CAD workers
- split CAD code into separate chunks
- warm up the worker only when user opens export/CAD actions
14. Recommended First PR Scope
Keep the first PR focused on migration mechanics only:
- create Vite app
- move current calculator code
- build static app
- switch Docusaurus page to iframe utility mode
- preserve existing behavior
Do not combine this with major formula changes or STEP generation. CAD/STEP work should be a follow-up PR using the new cad/ architecture.
15. Definition of Done
The migration is complete when:
BlindFlangeCalculatoris buildable as an independent Vite app- the built app is published to
static/utility-apps/blind-flange-calculator/app.html - the Docusaurus page uses
createUtilityPage('blind-flange-calculator') - current calculator functionality is preserved
- the codebase has a clear internal architecture for future improvements
- the app is ready to be moved to a separate repository without changing the public site route