Tech Stack Details¶
Complete technical specifications for FiberPath GUI technology stack.
Frontend Stack¶
React 18.3.1¶
Why React:
- Component-based architecture for modular UI
- Virtual DOM for efficient updates
- Large ecosystem of libraries and tools
- Strong TypeScript support
Key Features Used:
- Functional components with hooks
- Controlled form inputs
- Conditional rendering
- Effect hooks for side effects
- Memo for performance optimization
Example:
export function PlanForm() {
const project = useProjectStore(state => state.project);
const updateMandrel = useProjectStore(state => state.updateMandrel);
return (
<form>
<input
type="number"
value={project.mandrelParameters.diameter}
onChange={e => updateMandrel({ diameter: Number(e.target.value) })}
/>
</form>
);
}
TypeScript 5.0+¶
Why TypeScript:
- Catch errors at compile time
- IntelliSense for better DX
- Refactoring confidence
- Self-documenting interfaces
Configuration: (tsconfig.json)
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
Key Patterns:
- Discriminated unions for layer types
- Strict null checks
- Exhaustive switch statements
- Type guards for runtime safety
Vite 5.0¶
Why Vite:
- Instant dev server startup with ESM
- Lightning-fast HMR (Hot Module Replacement)
- Optimized production builds with Rollup
- Native TypeScript support
Configuration: (vite.config.ts)
export default defineConfig({
plugins: [react()],
clearScreen: false,
server: {
port: 5173,
strictPort: true,
},
build: {
target: "esnext",
minify: !process.env.TAURI_DEBUG,
},
});
Performance:
- Dev server starts in <1 second
- HMR updates in ~50ms
- Production build in ~10 seconds
Zustand 5.0.9¶
Why Zustand:
- Minimal boilerplate vs Redux
- No context providers needed
- Shallow selectors prevent re-renders
- DevTools integration
- TypeScript-first design
Store Pattern:
interface ProjectState {
project: FiberPathProject | null;
isDirty: boolean;
updateMandrel: (params: Partial<MandrelParameters>) => void;
}
export const useProjectStore = create<ProjectState>()(
devtools(
(set) => ({
project: null,
isDirty: false,
updateMandrel: (params) =>
set((state) => ({
project: {
...state.project!,
mandrelParameters: {
...state.project!.mandrelParameters,
...params,
},
},
isDirty: true,
})),
}),
{ name: "ProjectStore" }
)
);
Selector Pattern:
// ❌ Bad: Re-renders on any state change
const state = useProjectStore();
// ✅ Good: Re-renders only when diameter changes
const diameter = useProjectStore(
(state) => state.project?.mandrelParameters.diameter
);
// ✅ Better: Shallow comparison for objects
const mandrel = useProjectStore(
(state) => state.project?.mandrelParameters,
shallow
);
Zod 3.25.76¶
Why Zod:
- Runtime validation with TypeScript inference
- Composable schemas
- Clear error messages
- JSON schema generation
Schema Definition:
export const MandrelParametersSchema = z.object({
diameter: z.number().positive(),
windLength: z.number().positive(),
});
// TypeScript type inferred automatically
export type MandrelParameters = z.infer<typeof MandrelParametersSchema>;
Validation:
const result = MandrelParametersSchema.safeParse(data);
if (!result.success) {
console.error(result.error.issues);
// [{ code: 'too_small', minimum: 0, path: ['diameter'], message: '...' }]
}
@hello-pangea/dnd 18.0.1¶
Why Drag & Drop:
- Intuitive layer reordering in UI
- Accessible keyboard navigation
- Smooth animations
Usage (LayerManager):
<DragDropContext onDragEnd={handleDragEnd}>
<Droppable droppableId="layers">
{(provided) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
{layers.map((layer, index) => (
<Draggable key={layer.id} draggableId={layer.id} index={index}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
{layer.windType}
</div>
)}
</Draggable>
))}
</div>
)}
</Droppable>
</DragDropContext>
Desktop Shell¶
Tauri 2.0¶
Why Tauri:
- Small bundle size (~3-5 MB vs Electron's ~120 MB)
- Native webview (no embedded Chromium)
- Rust security and performance
- Cross-platform (Windows, macOS, Linux)
Architecture:
┌────────────────────────────────┐
│ WebView (React UI) │ JavaScript
├────────────────────────────────┤
│ Tauri IPC (invoke/listen) │ Async bridge
├────────────────────────────────┤
│ Rust Backend (Commands) │ Rust
│ - File I/O │
│ - Process spawning │
│ - Serial port access │
└────────────────────────────────┘
Key Features:
- Commands: Rust functions callable from JavaScript
- Events: Pub/sub for streaming updates
- File System: Secure path resolution
- Updater: Auto-update mechanism (v0.5.0+)
Security Model:
- Allowlist of permitted APIs
- No eval() or inline scripts
- CSP headers enforced
- Path traversal protection
Rust 1.70+¶
Why Rust:
- Memory safety without garbage collection
- Zero-cost abstractions
- Fearless concurrency
- Strong type system
CLI Integration:
use std::process::Command;
#[tauri::command]
fn plan_project(wind_def: String) -> Result<String, String> {
let output = Command::new("fiberpath")
.arg("plan")
.arg(&wind_def)
.output()
.map_err(|e| e.to_string())?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
Err(String::from_utf8_lossy(&output.stderr).to_string())
}
}
Streaming State:
pub struct MarlinState {
port: Option<SerialPort>,
is_connected: bool,
queue: VecDeque<String>,
}
impl MarlinState {
pub fn connect(&mut self, port_name: &str) -> Result<(), String> {
self.port = Some(SerialPort::open(port_name)?);
self.is_connected = true;
Ok(())
}
}
Testing Stack¶
Vitest¶
Why Vitest:
- Vite-native (same config, fast startup)
- Jest-compatible API
- ES modules support
- Watch mode with HMR
Features:
- Parallel test execution
- Coverage with v8
- Snapshot testing
- UI mode for exploration
React Testing Library¶
Why RTL:
- Focus on user behavior, not implementation
- Accessible queries (getByRole, getByLabelText)
- Async utilities (waitFor, findBy)
Example:
import { render, screen, fireEvent } from '@testing-library/react';
it('should update mandrel diameter', () => {
render(<MandrelForm />);
const input = screen.getByLabelText('Diameter');
fireEvent.change(input, { target: { value: '200' } });
expect(input).toHaveValue(200);
});
Build Tools¶
ESLint 8.x¶
Configuration:
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended"
],
"rules": {
"no-unused-vars": "error",
"@typescript-eslint/no-explicit-any": "warn"
}
}
Prettier 3.x¶
Configuration:
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5"
}
TypeScript Compiler¶
Key Flags:
strict: true- All strict checksnoUncheckedIndexedAccess: true- Array access safetynoUnusedLocals: true- Dead code detection
Version Matrix¶
| Package | Version | Purpose |
|---|---|---|
| react | 18.3.1 | UI framework |
| typescript | 5.0+ | Type safety |
| vite | 5.0.10 | Build tool |
| zustand | 5.0.9 | State management |
| zod | 3.25.76 | Runtime validation |
| @tauri-apps/api | 2.0.0 | Tauri bindings |
| @hello-pangea/dnd | 18.0.1 | Drag & drop |
| vitest | 1.0+ | Test runner |
| @testing-library/react | 14.0+ | Component testing |
Platform Support¶
Windows¶
- Minimum: Windows 10 1809+
- Webview: Edge WebView2 (bundled)
- Installer: MSI
macOS¶
- Minimum: macOS 10.15 Catalina
- Webview: WKWebView (native)
- Installer: DMG
Linux¶
- Minimum: Ubuntu 20.04, Fedora 36, Arch (current)
- Webview: webkit2gtk 4.1
- Installer: AppImage, DEB
Performance Characteristics¶
Bundle Size¶
- Development: ~15 MB
- Production: ~3-5 MB (Tauri) + ~2 MB (React bundle)
- Installer: ~10-15 MB
Startup Time¶
- Cold start: ~1-2 seconds
- Warm start: ~500ms
Memory Usage¶
- Idle: ~50-80 MB
- Active: ~100-150 MB
- Heavy use: ~200-300 MB
Build Time¶
- Dev server: <1 second
- HMR update: ~50ms
- Production build: ~10-15 seconds
- Full rebuild: ~20-30 seconds