Fluidd is a Vue 2.7 + TypeScript web interface for Klipper 3D printers that communicates with Moonraker via WebSocket.
- Vue 2.7 + Vuetify 2: UI framework with Material Design components
- Vuex Store: 28 namespaced modules mirroring Klipper/Moonraker domains (
printer/,files/,console/,macros/,webcams/,mmu/,spoolman/, etc.) - WebSocket Communication: Real-time JSON-RPC via custom
WebSocketClientinsrc/plugins/socketClient.ts - Component Structure: Class-style components with
vue-property-decorator; mixins-based architecture withStateMixinproviding common printer state access
All components use class-style decorators — no Options API or Composition API:
// Standard component
@Component({ components: { /* ... */ } })
export default class MyComponent extends Vue {
@Prop({ type: String, required: true })
readonly label!: string
@VModel({ type: Boolean })
open?: boolean
}
// Component needing printer state — extend via Mixins()
@Component({ components: { /* ... */ } })
export default class PrinterWidget extends Mixins(StateMixin) {
get klippyReady (): boolean {
return this.$typedGetters['printer/getKlippyReady']
}
}Available mixins (src/mixins/): StateMixin, FilesMixin, ServicesMixin, BrowserMixin, CameraMixin, ToolheadMixin, AfcMixin, MmuMixin
- Store modules in
src/store/— each hasstate.ts,getters.ts,mutations.ts,actions.ts,types.ts - Use
$typedStateand$typedGettersfor type-safe store access (defined insrc/plugins/filters.ts) - Use
Vue.set()for reactive dynamic state properties - Module definition:
export const auth = { namespaced, state, getters, actions, mutations } satisfies Module<AuthState, RootState>
- All printer communication through
SocketActionsinsrc/api/socketActions.ts(not direct HTTP) - Pattern:
baseEmit<T>(method, { dispatch, wait, params }) - Use
waitparameter for UI loading states:wait: Waits.onPrintStart - Wait constants defined in
src/globals.ts(Waitsobject, ~90 operation types) - Real-time updates handled via store mutations from socket events
- Auto-reconnect with configurable interval (
Globals.SOCKET_RETRY_DELAY)
- Auto-imported (no manual import needed): components in
src/components/common/,layout/,ui/— viaunplugin-vue-componentswithVuetifyResolver - Manual import required: widget components, view components
- Lazy-loaded:
EChartviaVue.component('EChart', () => import('./vue-echarts-chunk')) - Generated types:
components.d.tsat repo root — auto-generated, do not edit manually
- Node.js 24 — pinned in
.node-version(engines:^22.12.0 || ^24) - Vite 8 — build tool and dev server
@pedrolamas/plugin-vue2— Vue 2 SFC support for Viteunplugin-vue-components/rolldown— auto-imports components fromsrc/components/common|layout|uisass-embedded— SCSS preprocessor (variables auto-injected via@/scss/variables)- vitest v4 — unit test runner (jsdom environment)
commit-and-tag-version— release versioning (npm run release)- ESLint flat config (
eslint.config.mjs) — enforced at dev time viavite-plugin-checkerwithuseFlatConfig: true vite-plugin-checker— runs vue-tsc and ESLint during dev (disabled at build time)skott— circular dependency detection (npm run circular-check)- ES2020 lib target (
tsconfig.app.json) — no ES2021+ built-ins without polyfills
npm run bootstrap # Install git hooks (after clone)
npm run dev # Start development server (port 8080)
npm run build # Production build
npm run type-check # TypeScript validation (vue-tsc)
npm run lint # ESLint with Vue/TS rules
npm run test # Vitest unit tests
npm run circular-check # Check for circular dependenciessrc/
├── api/ # HTTP (axios) and WebSocket (custom JSON-RPC) clients
├── components/
│ ├── common/ # Shared dialogs & status components (auto-imported)
│ ├── layout/ # App shell: AppBar, AppDrawer, etc. (auto-imported)
│ ├── settings/ # Settings page components
│ ├── ui/ # Reusable: AppBtn, AppDialog, AppChart (auto-imported)
│ └── widgets/ # 27 feature widget dirs: bedmesh/, camera/, console/, filesystem/, macros/, mmu/, thermals/, toolhead/, etc.
├── directives/ # Custom Vue directives (v-safe-html for DOMPurify)
├── locales/ # i18n YAML files (23 languages)
├── mixins/ # Vue mixins (StateMixin, FilesMixin, etc.)
├── monaco/ # Monarch tokenizers and editor themes
├── plugins/ # Vue plugins (i18n, httpClient, socketClient, vuetify, filters)
├── router/ # Vue Router (hash mode) with auth guards
├── scss/ # Global styles and Vuetify variable overrides
├── store/ # 28 Vuex modules (printer, files, config, webcams, etc.)
├── types/ # UI-specific TypeScript types
├── typings/ # Global .d.ts declarations (Klipper, Moonraker namespaces)
├── util/ # Helper functions (30+)
├── views/ # Page components (Dashboard, Console, Jobs, etc.)
└── workers/ # Web Workers (parseGcode, mjpegStream, sandboxedEval, Monaco language providers)
- Hash-based routing (
#/path) - Views lazy-loaded via dynamic imports:
component: () => import('@/views/X.vue') - Auth guard via
defaultRouteConfigspread pattern;isAuthenticated()checksstore.state.auth - JWT token auth with auto-refresh (axios interceptors)
- Key routes:
/,/console,/jobs,/tune,/diagnostics,/timelapse,/history,/system,/configure,/settings,/camera/:cameraId,/preview,/login
- MDI icons via
@mdi/js— mapped insrc/globals.ts(Iconsobject, ~228 mappings) - Usage:
<v-icon>{{ $globals.Icons.close }}</v-icon> - Vuetify theme with custom dark/light overrides in
src/scss/variables.scss - PWA support with service worker in
src/sw.ts(Workbox, injectManifest strategy)
- Setup in
src/components/widgets/filesystem/setupMonaco.ts(includes worker environment setup) - Monarch tokenizers for
gcode,klipper-config,loglanguages (insrc/monaco/language/*.monarch.ts) - Custom CodeLens providers (links to Klipper docs from config sections)
- Document symbol provider for
klipper-config; folding range provider forklipper-configandgcode - Language providers run in dedicated Web Workers (
monacoCodeLensWorker,monacoDocumentSymbolsWorker,monacoFoldingRangesWorker)
- All printer commands via
SocketActionsmethods - Store updates from WebSocket events (not polling)
- File operations through Moonraker's file API (
src/store/files/) - HTTP client (
src/plugins/httpClient.ts) for file uploads, auth tokens
- Parent-child: Props down, events up
- Cross-component: Vuex store or
EventBus(src/eventBus.ts) - Flash messages:
EventBus.$emit(text, { timeout })— displayed byFlashMessagecomponent
import.meta.glob()used insrc/dynamicImports.tsfor lazy-loading:I18nLocales— locale YAML filesCameraComponents— camera service Vue components
- Views also dynamically imported in
src/router/index.tsvia() => import('@/views/X.vue')
- Unit tests in
src/util/__tests__/*.spec.tswith Vitest + jsdom - Global test functions (
describe,it,expect) —globals: truein vitest config - Setup file:
tests/unit/setup.ts - Time manipulation utility:
timeTravel(date, callback)intests/unit/utils.ts - Parameterized tests:
it.each([...])pattern - Test store actions/mutations independently from UI
- Source must pass linting with zero warnings and zero type errors — run
npm run lintandnpm run type-checkbefore committing - Vue class-style components with
vue-property-decorator(@Component,@Prop,@VModel,Mixins()) - ESLint enforced:
neostandard+pluginVue.configs['flat/vue2-recommended']+pluginRegexp+@vue/eslint-config-typescript .editorconfigrules: 2 spaces, LF line endings, UTF-8, trim trailing whitespace, max line 100 (code)- camelCase for variables/methods, PascalCase for components
- Use
consolafor logging, notconsole.log(configured insrc/setupConsola.ts— warn in prod, verbose in dev) - Type imports:
import type { ... }for types only (verbatimModuleSyntax: true) satisfieskeyword for store module type checking
- Conventional commits required:
feat,fix,docs,style,refactor,perf,test,build,ci,chore,revert,types,i18n - Commit subject max 50 characters — hard-enforced by
.husky/commit-msghook - Signed-off-by line required on all commits (use
git commit -s):Signed-off-by: Your Name <your@email> - PR titles must follow conventional commits — CI-enforced via
amannn/action-semantic-pull-request(scope optional) - PR branches must be off a branch other than
developormaster - Clean develop preferred: squash and rebase feature branches prior to merge
- CHANGELOG visibility: only
feat,fix,perf,refactorappear inCHANGELOG.md(configured in.versionrc.json) - CI pipeline order:
npm ci→lint --no-fix→type-check→test:unit→circular-check→build
- Vue 2.7 limitations: no Composition API in production builds
- WebSocket reconnection handled automatically by
socketClient.ts - File uploads use FormData with progress tracking in store
- Dynamic imports for code splitting (see
vue-echarts-chunk.ts,src/dynamicImports.ts) - SCSS deprecation warnings silenced:
import,global-builtin,slash-div,if-function @/scss/variablesauto-injected into all SCSS/Sass files via Vite configpathaliased topath-browserifyfor browser compatibility- Strict Vuex mode enabled only in dev (
strict: import.meta.env.DEV) - SVG files auto-optimized on commit — pre-commit hook runs SVGO on staged
.svg,.vue, andsrc/globals.tsfiles VUE_env prefix required — only env vars prefixedVUE_are exposed to app code viaimport.meta.env(ViteenvPrefix)import.meta.env.VERSIONandimport.meta.env.HASH(short git hash) are injected at build timeserver/config.jsonis the runtime config source (deployed asdist/config.json) — contains theme presets, endpoints, hosted flag- Translations managed via Weblate — do not directly edit non-English locale files in
src/locales/
- VSCode Dev Container (
.devcontainer/) bundles adocker-klipper-simulavrcontainer — real Klipper/Moonraker simulation on port 7125, Fluidd on port 8080 postCreateCommandrunsnpm ci && npm run bootstrapautomatically
- Zensical (Material for MkDocs successor) — static site generator in
docs/ - Config:
docs/zensical.toml— nav, theme, extensions, social links - Content:
docs/docs/— Markdown files with YAML frontmatter - Overrides:
docs/overrides/— custom Jinja2 templates (header, htmltitle) - Custom CSS:
docs/docs/stylesheets/extra.css— Fluidd brand colors - Glossary:
docs/includes/glossary.md— abbreviation tooltips auto-appended to all pages - Lint:
markdownlint --config docs/.markdownlint.json docs/docs/ - Install:
cd docs && python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt - Build:
cd docs && zensical build --clean - Serve:
cd docs && zensical serveornpm run serve:docs(localhost:8000) - Deploy: GitHub Actions (
.github/workflows/docs.yml) — builds on push tomaster, deploys to gh-pages withdocs.fluidd.xyzCNAME
docs/
├── docs/ # Markdown content
│ ├── index.md # Homepage
│ ├── getting-started.md # Installation (KIAUH, Docker, Manual, fluidd.xyz, FluiddPI)
│ ├── configuration.md # Fluidd Config, Klipper, Moonraker, Multiple Printers
│ ├── customize.md # Layout, themes, hiding components
│ ├── features/
│ │ ├── index.md # Features overview (section landing page)
│ │ ├── authorization.md
│ │ ├── cameras.md
│ │ ├── console.md
│ │ ├── diagnostics.md
│ │ ├── file-editor.md # Monaco editor features, syntax, CodeLens, folding
│ │ ├── file-manager.md # File browser, upload, search, previews, drag-and-drop
│ │ ├── job-queue.md # Sequential printing queue
│ │ ├── keyboard-shortcuts.md # Global, editor, console keyboard shortcuts
│ │ ├── localization.md
│ │ ├── macros.md
│ │ ├── multi-material.md # Multiple extruders + Spoolman
│ │ ├── multiple-printers.md
│ │ ├── printing.md # G-code viewer, thumbnails, bed mesh, print history
│ │ ├── slicer-uploads.md
│ │ ├── system-and-notifications.md # System info + notifications
│ │ ├── thermals.md # Chart, presets, sensors
│ │ ├── third-party-integrations.md # Kalico, Happy Hare, AFC, Beacon, Obico, OctoEverywhere, etc.
│ │ ├── timelapse.md
│ │ └── updates.md
│ ├── development.md # Dev container, local dev, localization
│ ├── faq.md # Organized by topic (Setup, Cameras, System, Printing)
│ └── sponsors.md
├── includes/
│ └── glossary.md # Abbreviation definitions (auto-appended)
├── overrides/ # Jinja2 template overrides
├── zensical.toml # Site configuration
└── .markdownlint.json # Lint rules (MD013 and MD025 disabled)
- Frontmatter:
title(required),icon(top-level pages only, Lucide icons) - Images:
/assets/images/path, stored indocs/docs/assets/images/ - Code blocks must always have a language tag:
inifor Klipper/Moonraker config,bashfor shell commands,jsonfor JSON,textwhen no language applies - Zensical uses Python-Markdown which requires 4-space indentation per nesting level for all block-level elements nested in lists (sub-lists, paragraphs, code blocks, blockquotes) — no tabs
- Tables must use aligned pipe style (columns padded to equal width)
- Links: use
{.md-button}attribute for standalone action links - Keys: use
++key++syntax (pymdownx.keys extension) instead of<kbd> - Terminology: G-code (not gcode/Gcode), Wi-Fi (not WiFi), GitHub (not Github), Node.js (not NodeJS), SD card (not SDCard), em dash (—) not hyphen (-) for parenthetical dashes
- Klipper macro names: format as inline code (e.g.,
PAUSE,SET_PAUSE_AT_LAYER,_CLIENT_VARIABLE) - Klipper/Moonraker section names and config variable names: format as inline code (e.g.,
[virtual_sdcard],enable_object_processing) — exception: leave unformatted when used as markdown headings - Glossary terms (AFC, API, CNC, CORS, JWT, MCU, MMU, MPC, PID, etc.) get automatic tooltips via
docs/includes/glossary.md - When introducing acronyms in docs, check if they exist in the glossary — if not, assess whether they should be added (domain-specific or non-obvious acronyms: yes; universally known ones like USB, HTTP, CPU: no)
- Before committing docs changes, always run:
markdownlint --config docs/.markdownlint.json docs/docs/— must be cleancodespell docs/docs/— must be clean
- Be extremely concise in responses
- Sacrifice grammar for brevity
- Focus on essential info only