Agent skill

Flowfile Frontend Conventions

by Edwardvaneechoud in Edwardvaneechoud/Flowfile

Vue 3 renderer + Tauri 2 shell conventions for flowfilefrontend and flowfilewasm — path aliases, Pinia store map, the axios trailing-slash 307 trap, the node-settings-by-glob-convention resolution…

MITAuto-check passedFrontend & Design

Install Flowfile Frontend Conventions

skills CLI
$ npx skills add Edwardvaneechoud/Flowfile --skill flowfile-frontend-conventions -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install Edwardvaneechoud/Flowfile flowfile-frontend-conventions --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/Edwardvaneechoud/Flowfile.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/flowfile-frontend-conventions .claude/skills/flowfile-frontend-conventions && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
flowfile-frontend-conventions
GitHub stars
375
Token cost
~8.5k tokens
SKILL.md length
3,500 words
Files
1
Skills in repo
19
Repo updated
First seen
Licence
MIT

At a glance

Vue 3 renderer + Tauri 2 shell conventions for flowfilefrontend and flowfilewasm — path aliases, Pinia store map, the axios trailing-slash 307 trap, the node-settings-by-glob-convention resolution…

  • Works in 12 steps: Renderer layout & path aliases → Pinia stores — inventory and the… → Axios conventions — and the… → …
  • Editing a Vue component/view/store/route in flowfilefrontend
  • SKILL.md covers When NOT to use this skill, 1. Renderer layout & path…, 2. Pinia stores — inventory… and 3. Axios conventions — and the…, plus 10 more sections
  • Calls npm, docker and make

What it does

Flowfile Frontend Conventions is an agent skill from Edwardvaneechoud/Flowfile. Vue 3 renderer + Tauri 2 shell conventions for flowfilefrontend and flowfilewasm — path aliases, Pinia store map, the axios trailing-slash 307 trap, the node-settings-by-glob-convention resolution system, VueFlow canvas wiring, desktop.ts as the sole Tauri boundary, the sidecar boot/readiness/shutdown ladder, the 19-file god-component TODO(refactor) policy, and WASM's explicit-run-only rule. Use when adding or editing a Vue component/view/store/route in flowfilefrontend, building a new node's settings UI…

Its SKILL.md is about 8.5k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Frontend & Design, covering Third-party API integration. It works with Vue.js, Tauri, WebAssembly and Vitest. The repository describes itself as: Flowfile is a visual ETL tool and Python library combining drag-and-drop workflows with Polars dataframes. Build data pipelines visually, define flows programmatically with a… The licence is MIT.

When your agent uses it

  • Editing a Vue component/view/store/route in flowfilefrontend
  • Building a new nodes settings UI
  • Touching axios API wrappers
  • Seeing unexplained 307s

Example prompts

  • “/flowfile-frontend-conventions”

Requirements

  • Python 3
  • Docker

Workflow steps

12 steps, taken from the step headings in SKILL.md.

  1. Renderer layout & path aliases
  2. Pinia stores — inventory and the signal-counter pattern
  3. Axios conventions — and the trailing-slash 307 trap
  4. Node UI system — resolved by naming convention, not a registry
  5. VueFlow canvas integration
  6. lib/desktop.ts — the sole Tauri boundary
  7. Tauri shell summary — and why it's the riskiest surface in this package
  8. God-component policy — pre-written extraction plans
  9. Web vs. desktop mode differences
  10. flowfile_wasm hard rules
  11. Three traps worth knowing by name before you hit them
  12. Lint, format, and test commands

What it can do on your machine

Read from SKILL.md and the folder at commit 13aa287. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • npm
    • docker
    • make

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md. Its commands use npm and docker, which can reach the network depending on how they are called.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Flowfile Frontend Conventions loads about 8.5k tokens when it runs. Until then it costs about 202 tokens; SKILL.md has 3,500 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~202
When it runs · the whole SKILL.md, loaded when a task matches
~8.5k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from Edwardvaneechoud/Flowfile at commit 13aa287, republished under its MIT licence (© Edwardvaneechoud). 3,500 words, ~8,531 tokens.

Download SKILL.mdSave it as .claude/skills/flowfile-frontend-conventions/SKILL.md (or your agent's skills folder).
name
flowfile-frontend-conventions
description
Vue 3 renderer + Tauri 2 shell conventions for flowfile_frontend and flowfile_wasm — path aliases, Pinia store map, the axios trailing-slash 307 trap, the node-settings-by-glob-convention resolution system, VueFlow canvas wiring, desktop.ts as the sole Tauri boundary, the sidecar boot/readiness/shutdown ladder, the 19-file god-component TODO(refactor) policy, and WASM's explicit-run-only rule. Use when adding or editing a Vue component/view/store/route in flowfile_frontend, building a new node's settings UI, touching axios API wrappers or seeing unexplained 307s, changing anything under src-tauri/ (sidecar, lifecycle, capabilities), calling a Tauri/native API from renderer code, working in flowfile_wasm, or wiring the ShareDialog sharing UI onto a new connection view.

Flowfile frontend conventions

This skill covers flowfile_frontend/ (Vue 3 renderer + Tauri 2 desktop shell) and flowfile_wasm/ (Pyodide browser-only build). It is about how the frontend is put together and where its landmines are — not about the backend it talks to.

When NOT to use this skill

  • Backend/core/worker/kernel contracts, secrets format, worker offload wire protocol → flowfile-architecture-contract.
  • Adding a new node type end-to-end (backend template, settings schema, add_<type> method) → flowfile-node-development (this skill only covers the frontend half: where the settings .vue file goes and how it's loaded).
  • flowfile_frame Python API / codegen → flowfile-frame-and-codegen.
  • AI subsystem internals (agents, providers, BYOK) → flowfile-ai-subsystem.
  • Running/building the whole stack, Docker Compose, ports → flowfile-build-and-env / flowfile-run-and-operate.
  • Env vars and feature flags in detail → flowfile-config-and-flags.
  • Writing/running Vitest or Playwright tests → flowfile-testing-and-validation.
  • Diagnosing a live symptom step-by-step → flowfile-debugging-playbook.
  • Known historical bugs as a searchable archive → flowfile-failure-archaeology.
  • Change-control process (drift gates, review, release tags) → flowfile-change-control — nothing here overrides it.

1. Renderer layout & path aliases

Vite's root is flowfile_frontend/src/renderer/ (not the package root), entry index.html (vite.config.mjs:22). Inside that root:

src/renderer/
  main.ts             # bootstrap: createApp, Pinia, router, i18n, Element Plus,
                       # theme init, then setupService -> authService -> auth init -> app.mount("#app")
  config/constants.ts    # axios baseURL + GA OAuth callback URL resolution
  config/environment.ts  # ENV flags derived from NODE_ENV
  lib/desktop.ts       # THE ONLY renderer<->Tauri bridge (see §6)
  app/                 # '@' alias target — all feature code lives here
    App.vue, api/, components/, composables/, features/, layouts/,
    pages/, router/, services/, stores/, types/, utils/, views/

Path aliases exist in three files and must stay in sync — adding or renaming one in only one file silently breaks the dev server, the TypeScript checker, or Vitest (whichever file you forgot):

FileAliases defined
vite.config.mjs (~:83-91)@ → src/renderer/app, plus @/api, @/types, @/stores, @/composables
tsconfig.json@/* glob, plus the same four named ones with /* variants
vitest.config.tsonly the bare @

Router: app/router/index.ts uses createWebHashHistory (URLs look like #/main/designer). Routes are lazy import()ed; most live under the /main AppLayout parent, which carries meta.requiresAuth: true by default. /setup and /login opt out.


2. Pinia stores — inventory and the signal-counter pattern

Files live in app/stores/, kebab-case xxx-store.ts (one legacy exception: fileBrowserStore.ts). index.ts is the Pinia plugin.

Store (id)Role
flowflowId (persisted in sessionStorage['last_flow_id']), the live vueFlowInstance, undo/redo historyState, and two monotonic signal counters (see below).
nodecurrent nodeId, nodeData cache, updateSettings (the POST /update_settings/ path — see §4), many @deprecated proxy getters to flow/editor/results for legacy callers.
editordrawer open/active-component state, shared code-editor buffer, graphVersion (dirty-state counter), plus "request token" signals like nodeSettingsOpenRequest {nodeId, token}.
resultsrun results per flow/node, resultVersion.
auth, themeuser/session; light/dark/system (localStorage flowfile-theme-preference).
columndeprecated shim — re-exports useNodeStore; Canvas.vue still imports it under this name for back-compat. Don't add new state here.
Feature storesfileBrowserStore, global-store, project-store, catalog-store (829 LOC), dashboards-store, sharing-store, tutorial-store, notebook-store, community-nodes-store, node-designer-store, notifications-store, sql-editor-store, telemetry-store, update-store, drawer-store (full list: ls app/stores).
AI storesai-store (1666 LOC), ai-agent-store, ai-diff-store, ai-command-palette-store, ai-ghost-node-store, ai-autocomplete-store, ai-code-generator-store (+ *-persistence.ts siblings). These have colocated *.test.ts — Vitest picks up src/**/*.test.ts only.

The signal-counter pattern (flow-store): pendingReloadCounter + requestReload() means "re-read the graph from core". It is used after an out-of-band backend mutation (AI apply), after every undo/redo, after a save that changes edges server-side (RunFlow), and after any failed mutation (the mutation channel requests it for every refused ordered request; see §3) — the canvas never patches itself back. Canvas.loadFlow flushes pending edits once, waits for the mutation queue, re-reads if mutationGeneration() moved, and keeps viewport and selection when reloading the same flow. Canvas.vue watches the counter, not a boolean, so that two reload requests in the same tick still both fire (a boolean flag toggled twice collapses to a no-op watch). pendingLayoutResetCounter / requestLayoutReset() is the same idea for re-running auto-layout. Use this pattern for any new "something changed, someone downstream should react" signal — don't reach for a boolean.

editorStore.graphVersion is different: bumped by nodeStore.updateSettings after every successful save and by flowStore.updateHistoryState for every mutation response carrying history, it represents dirty state ("something on the canvas changed"), not "reload from the network."


3. Axios conventions — and the trailing-slash 307 trap

app/services/axios.config.ts sets axios.defaults.baseURL and withCredentials = true. A request interceptor injects Authorization: Bearer <token> unless the request carries header X-Skip-Auth-Header; a response interceptor retries once on 401 after a token refresh, else calls authService.logout(). A third pair, services/mutationChannel.ts, is installed after them (request interceptors run in reverse): it sends graph mutations one at a time in issue order, applies each response's history centrally, and requests a canvas reload for every ordered request core refused. Multi-part gestures are one FlowApi.applyOperations batch built with utils/graphOperations.ts — see flowfile_frontend/CLAUDE.md ("Graph mutations", "Undo/redo and the settings drawer").

BaseURL resolution (config/constants.ts):

ts
export const flowfileCorebaseURL = isDesktop
  ? `http://127.0.0.1:${resolveCorePort()}/`
  : `${window.location.origin}/api/`;

Desktop's port comes from window.__FLOWFILE_PORTS__, injected by the Rust shell before any renderer script runs (see §7). The base must be absolute — the AI streaming clients do new URL(path, base), which throws on a relative base.

The rule, stated once because it costs real debugging time

A frontend axios call path must match the FastAPI route string exactly, trailing slash included. FastAPI issues an absolute 307 redirect when the slash doesn't match. Verified pairs in this codebase:

  • node.api.ts posts /update_settings/ ↔ routes.py @router.post("/update_settings/")
  • node.api.ts posts /node/description/ ↔ routes.py @router.post("/node/description/")
  • node.api.ts posts /node/reference/ ↔ routes.py @router.post("/node/reference/")

Why this bites in production and nowhere else: two proxy layers happen to paper over a mismatch during development, so the bug reaches Docker before anyone notices. vite.config.mjs's /api proxy strips the /api prefix and its configure(proxy) hook rewrites the backend's Location header back to /api/... (replicating nginx's default proxy_redirect); nginx.conf (Docker) proxies /api/ → core without overriding Host, so nginx's own default proxy_redirect also rewrites the Location back to the external URL; and pytest's TestClient follows redirects transparently too. The upshot: a slash mismatch works in npm run dev:web, works under pytest, and only breaks in a real Docker deployment. Fix it frontend-side — make the axios path match the decorator, not the other way around. Verify via docker compose logs flowfile-core | grep " 307 " after exercising the endpoint — a 307 there on a route you expected to be a 200/201/422 is this bug.

API wrapper files: app/api/*.api.ts (one per resource: node, flow, catalog, secrets, shares, userGroups, …) are static-method classes importing the configured axios instance from ../services/axios.config. app/services/ additionally holds auth.service.ts, setup.service.ts, user.service.ts, and the SSE clients aiStreamClient.ts / aiDiffClient.ts.


4. Node UI system — resolved by naming convention, not a registry

There is no static node-component registry. Settings components are found by string-interpolating a path and globbing it with import.meta.glob — fast to extend, zero build-time safety net if you get a name wrong.

Node templates come from the backend (GET /node_list); the frontend's NodeTemplate type is app/types/flow.types.ts. app/composables/useNodes.ts fetches and caches them once per session; the palette filters prod_ready nodes when environment.ts says production.

Two independent globs point at the same component tree and must stay in sync if you ever restructure the directory:

  1. app/composables/useDragAndDrop.ts — import.meta.glob("../components/nodes/node-types/elements/**/*.vue"). getComponent() builds the path elements/${camelCase(item)}/${TitleCase(item)}.vue and resolves the component rendered inside the VueFlow node itself.
  2. app/components/nodes/GenericNode.vue — same glob pattern, own relative path, resolves the component for the settings drawer (defineAsyncComponent, 3000ms timeout, retries ≤3, logs console.error on a missing path — this is a runtime failure, not a build error).

There are 46 element directories / 87 .vue files under app/components/nodes/node-types/elements/ today (count drifts as nodes are added — re-run the command in Provenance to check). getComponents.ts (singular get, no s on Nodes) is unrelated — it only lazy-loads elements/manualInput/*.vue editor cells for that one node's table editor.

Trap: composables/useNodes.ts also exports a getComponent that globs ../features/designer/nodes/elements/**/*.vue — that directory does not exist. It has no live callers today but will throw "Component not found" if anyone imports it. Use the useDragAndDrop loader or the GenericNode path, never this one.

Add-a-node checklist (frontend half only)
  1. Backend side (template, settings schema, add_<type>) is flowfile-node-development's job — do that first.
  2. Drop the icon <item>.svg into app/features/designer/assets/icons/.
  3. Create the settings component at exactly app/components/nodes/node-types/elements/<camelCase(item)>/<TitleCase(item)>.vue — TitleCase capitalizes each _-separated word and joins, e.g. text_to_rows → textToRows/TextToRows.vue. A wrong directory/filename fails silently at runtime, not at build time.
  4. Follow the Filter.vue pattern: wrap content in <generic-node-settings v-model="..." @request-save="saveSettings">, destructure useNodeSettings({ nodeRef, onBeforeSave }) for saveSettings/pushNodeData/handleGenericSettingsUpdate, implement loadNodeData(nodeId) hydrating from nodeStore.getNodeData, and end with defineExpose({ loadNodeData, pushNodeData, saveSettings }) — the drawer host (NodeSettingsDrawer.vue) calls these two names by contract. Never save from loadNodeData (drawers never save on open): load-time fix-ups stay in the draft. The drawer's close saves only user edits, plus what a never-configured node shows — decided by the drawer from the loaded response's NodeData.is_setup, not from your draft. Handles that depend on settings follow the saved settings (set them on load and in onAfterSave, never on an unsaved toggle).
  5. No registration file to edit — both globs above pick it up automatically since they point at the same tree.
  6. prod_ready: false on the backend template hides it from the production palette but keeps it loadable in already-saved flows (the full list is cached; the filter is applied per-consumer, not server-side).

5. VueFlow canvas integration

@vue-flow/core (^1.42) + @vue-flow/minimap. Canvas.vue (see §8 — it's one of the god components) owns the useVueFlow() instance and stores it in flow-store so other components/stores can findNode/mutate handles directly (e.g. a node component rewriting its own output handles for a split-output mode).

  • Node ids on the canvas are the backend's numeric node ids, as strings. data carries {id, label, component (markRaw), inputs/outputs (NodeHandle[]), nodeTemplate}.
  • Handle counts come from utils/nodeHandles.ts deriveHandles, driven by template input/output counts plus dynamic_inputs/output_names. multi: true templates (union, polars_code) render one input handle that accepts many edges, not N handles.
  • Groups are VueFlow type: "group" nodes (composables/useNodeGroups.ts); collapsing swaps real edges for synthetic "proxy" edges. VueFlow has no @pane-dblclick event — Canvas.vue listens for the native DOM event directly instead.
  • The canvas holds no unsaved graph state: send the gesture first (one FlowApi.applyOperations when it changes several things) and change the canvas only after core accepts it. Drags and arrow-key nudges send the whole moved selection; a failed mutation reloads instead of patching back.
  • The right-hand drawer (Settings/Results tabs) is a declarative registry, views/DesignerView/drawerRegistry.ts — its own comment calls it "single source of truth... adding/moving a view is a one-entry edit here." Add new tabs there, not by hand-wiring a drawer component. A tab that hosts CodeMirror must defer it until visible (gate it on an active prop derived from drawer.activeTab) — it breaks if constructed while hidden. The code dock needs no such gate: DesignerView.vue v-if-mounts the .code-dock aside only while it is shown. The code generator is not a drawer tab: it is the resizable code-dock split pane in DesignerView.vue (toggled by editorStore.showCodeGenerator), holding FlowFrame | Polars | Project | Notebook.

6. lib/desktop.ts — the sole Tauri boundary

Rule (enforced by convention, not lint): view/component code must never import @tauri-apps/* directly. Every native capability is wrapped in src/renderer/lib/desktop.ts with an explicit web-mode fallback, gated on isDesktop = typeof window !== "undefined" && !!window.__TAURI_INTERNALS__.

MethodDesktopWeb fallback
getAppVersion()Tauri app.getVersion() / invoke get_app_version""
quitApp()invoke quit_app (Rust: graceful shutdown then exit)no-op
openOauth(url)invoke open_oauth → modal Tauri webview, resolves captured codewindow.location.assign(url)
openExternal(url)invoke("plugin:opener|open_url") — system browserwindow.open(url, "_blank", "noopener")
readClipboardText()dynamic import("@tauri-apps/plugin-clipboard-manager").readText()navigator.clipboard.readText()
onViewZoom(h)listen view:zoom event from the native menuno-op

Never call navigator.clipboard.readText() directly in renderer code. In WKWebView (Tauri's macOS webview), that call pops the native macOS "Paste" confirmation pill on every programmatic read. Always go through desktop.readClipboardText(), which uses the clipboard-manager plugin (NSPasteboard access) and never triggers it. This is why canvas paste (useDragAndDrop.ts's createManualInputFromClipboard) routes through desktop.ts instead of calling the browser API. The general principle behind this whole module: privileged operations belong on the native (Rust) side of the boundary, invoked through one narrow, auditable seam — not sprinkled through view code as direct browser/Tauri API calls.

To add a new native capability: add the Rust command to commands.rs and list it in lib.rs's tauri::generate_handler![...] (see the current set with the generate_handler grep in Provenance) — or, for a plugin command (opener, clipboard-manager) instead of a custom Rust command, add its permission string to src-tauri/capabilities/main.json's "permissions" array (no Rust code needed, but the invoke silently fails without the grant). Either way, wrap it in desktop.ts with a web-mode fallback; never call window.__TAURI__.* or @tauri-apps/* from anywhere else.


7. Tauri shell summary — and why it's the riskiest surface in this package

Modules under src-tauri/src/: lib.rs (entry/lifecycle), sidecar/{mod.rs, readiness.rs, shutdown.rs}, commands.rs, menu.rs, oauth.rs, state.rs, env.rs, window.rs.

Boot, in order: plugins register (log, opener, process, os, clipboard-manager, window-state, updater) → setup emits services-status {status:"starting"} and sidecar::start_services spawns core+worker on a scanned free port pair (core = 63578 + k*2, worker = core + 1, k in 0..100; binaries resolve from src-tauri/binaries/ in dev or <resource_dir>/binaries/ in release, staged there by make services; each process gets FLOWFILE_MODE=electron + FLOWFILE_SUPERVISOR_PID=<shell pid> injected, the latter letting shared/parent_watcher.py self-reap if the shell itself is SIGKILLed) → readiness polls GET http://127.0.0.1:<port>/docs every 1s up to a 120s deadline (core and worker awaited concurrently; cold onedir bundles + first-launch AV scanning can be slow) → on success the main window is created programmatically in Rust (not via tauri.conf.json's static window list) so an initialization_script can inject window.__FLOWFILE_PORTS__ = Object.freeze({core, worker}) before any renderer script runs — the only place that value comes from. On readiness failure, kill_spawned does a PID-only kill, no HTTP — a failed readiness often means the port itself is unresponsive, and in the NoFreePortPair edge case the recorded ports might belong to a different running Flowfile instance, so POSTing /shutdown there could kill someone else's process.

Shutdown ladder (sidecar/shutdown.rs, best-effort + idempotent): POST http://127.0.0.1:<port>/shutdown to core AND worker in parallel (3s timeout each) → sleep 2s for natural exit → Unix killpg(SIGTERM) on the whole process group (a bare kill would orphan the worker's multiprocessing children) / Windows taskkill /T /PID → poll liveness every 100ms up to 5s → still alive → killpg(SIGKILL) / taskkill /F /T.

macOS Cmd+Q / dock-quit is a distinct code path from closing the window — it surfaces as tao's RunEvent::Exit, not RunEvent::ExitRequested. The shell's run loop matches both arms; if a future edit only handles one, the other quit path leaks sidecars silently (this has happened before).

Show full SKILL.md (1,281 more words)Show less
This is among the riskiest edit surfaces in the whole repo

src-tauri/src/sidecar/* has zero desktop end-to-end test coverage — the old Electron-era app.spec.ts/complex-flow.spec.ts suites were deleted in the Tauri migration and never replaced (open TODO at the top of tests/web-flow.spec.ts). Two known races are documented in-source as TODO comments, not fixed: (1) quit-during-startup — the app can quit while start_services is still spawning, shutdown running before a sidecar's PID is recorded, so that spawn is never reaped; (2) pid-not-cleared-during-shutdown — a sidecar that terminates during shutdown can leave a stale PID a later killpg could target after the OS recycles it onto an unrelated process.

Consequence: editing anything under sidecar/, lib.rs's lifecycle handlers, or env.rs has no automated test to catch a regression. Manually verify both quit paths after any change — launch, quit via Cmd+Q (or app menu/dock); relaunch, quit via the window close button; after each, confirm nothing survived:

bash
ps aux | grep -i flowfile | grep -v grep   # expect: no flowfile_core-*/flowfile_worker-* rows

8. God-component policy — pre-written extraction plans

A number of files in this package carry a TODO(refactor) header comment with a pre-written extraction plan (what to pull out, into what, at roughly which lines) — among them views/DesignerView/Canvas.vue, composables/useDragAndDrop.ts, views/CatalogView/CatalogView.vue (~1700 LOC), views/AdminView/AdminView.vue, components/common/DraggableItem/DraggableItem.vue, and four node-types/elements/* settings components (databaseReader, databaseWriter, googleAnalyticsReader, pythonScript). Get the full current list with the grep in Provenance.

Example — Canvas.vue's header:

ts
// TODO(refactor): ~1170 LOC; bundles 7+ concerns. Plan to extract:
//   - 6 draggable panel wrappers (~lines 927-1021) → individual *Panel components
//   - clipboard/copy-paste logic (~lines 533-700) → useFlowClipboard composable
//   - context menu handling (~lines 702-794) → useContextMenu composable
//   - keyboard shortcuts (~lines 729-778) → useFlowHotkeys composable

The rule when you touch one of these files:

  • Doing a substantial edit near a documented seam? Follow the plan already at the top of the file — it was written by someone who read the whole thing; extract into the named composable/component it specifies.
  • Never invent your own decomposition that diverges from the written plan without discussing it — two different extraction shapes for the same file compound the mess instead of fixing it.
  • Never leave behind a deprecated shim path once you do extract — this codebase already carries intentional back-compat shims (views/DesignerView/useNodes.ts, useDnD.ts marked "DEPRECATED: Import from '@/composables'", stores/column-store.ts) from past refactors; don't add another one you don't need to.
  • A small, surgical fix that doesn't touch the plan's seams does not require doing the whole refactor — the header is a map for when someone eventually does the extraction, not a blocking requirement on every edit.

9. Web vs. desktop mode differences

ConcernWeb (vite dev / Docker nginx)Desktop (Tauri)
API base<origin>/api/ (proxied, replicates 307 rewrite)http://127.0.0.1:<injected port>/ direct
DetectionisDesktop falsewindow.__TAURI_INTERNALS__ present
Authfull login flow, unauth → /loginFLOWFILE_MODE=electron auto-issues tokens, no login redirect
Portsfixed 63578scanned pair; multiple app instances can coexist
Clipboard readnavigator.clipboard (browser permission prompt)clipboard-manager plugin (no macOS pill)
External links / OAuthwindow.open / location.assignopener plugin (system browser) / modal oauth window
App versionVite __APP_VERSION__ defineget_app_version command
/project, /user-groups, /shares routersgated by FLOWFILE_MODE/FLOWFILE_ENABLE_PROJECTS server-side (404 in electron for the sharing routers)projects always on in electron; sharing routers still 404

10. flowfile_wasm hard rules

Separate npm package flowfile-editor, pure Vue 3 + VueFlow + Pyodide-in- browser — no backend, no axios; nothing in §1-§9 above applies here.

  • Execution is explicit-only. Only four actions may run data: Run flow (toolbar/Ctrl+E/lib run()), Run Now (node context menu), Apply (settings drawer), Fetch data (table preview button). Selecting, opening a panel, clicking, dragging, dropping, or pasting a node must never trigger an execute_* Python bridge call — regression-tested by tests/unit/no-auto-run.test.ts. Adding a new node-touching affordance? Ask "does this run data?" before wiring its handler.
  • Node count: flowfile_wasm/src/config/nodeCatalog.ts is the source of truth (the counts in core's generated wasm_node_support.json mirror it) — re-verify before quoting a number. As of 2026-09 there are 24 runnable node types across 6 palette categories (Machine Learning exists but every node in it is locked/greyed-out), plus 17 locked teaser types (available: false) hidden from the default browse view and shown on search or via the "Show full-app nodes" toggle.
  • Pyodide is pinned to v0.27.7 (CDN script, not an npm dependency) — the last release shipping a Polars wheel. Bumping it breaks loadPackage(['polars', 'pydantic']).
  • Pyodide needs SharedArrayBuffer, gated behind COOP/COEP response headers (Cross-Origin-Opener-Policy: same-origin, Cross-Origin-Embedder-Policy: require-corp); the dev server sets these and any embedder hosting this library must set them too or Pyodide fails to load.
  • Parquet never reaches Python as Parquet — the wasm Polars build has Parquet compiled out (Arrow IPC only). parquet-wasm converts Parquet↔IPC via a deliberately bundler-opaque dynamic import so webpack5/esbuild don't try to statically resolve a literal https:// URL.
  • The engine Python package (src/pyodide/engine/) runs identically under CPython pytest, real Pyodide, and the browser — new node logic must work under both. Add the execute_<type> case to flow-store.ts's executeNode dispatcher plus an entry in Canvas.vue's getSettingsComponent map (WASM uses an explicit map, unlike the main app's glob convention in §4).

11. Three traps worth knowing by name before you hit them

StatsPanel duplicates RunOverviewPanel/ScheduleOverviewPanel — forward emits through all three hops

CatalogView.vue's default "overview" tab renders StatsPanel.vue, which mounts its own, separate copies of RunOverviewPanel and ScheduleOverviewPanel — pixel-identical to the dedicated Runs/Schedules tabs CatalogView.vue also mounts directly. An event from the inner panel (@view-run, @open-snapshot, @toggle-schedule, …) only reaches CatalogView's handler if forwarded at every hop: panel → StatsPanel (@x="$emit('x', $event)" + a matching defineEmits entry) → CatalogView's binding on the <StatsPanel> tag. Miss one hop and the click dies silently on the stats-tab mount only — the identical click on the dedicated Runs/Schedules tab keeps working, which is what makes this bug confusing to reproduce. Adding a new emit to RunOverviewPanel or ScheduleOverviewPanel? Grep both StatsPanel.vue and CatalogView.vue and wire all three places, not just the one you're staring at.

el-dialog's @open does not fire for a dialog mounted already-open

If a parent does v-if="show" and sets show = true in the same code path (dialog is created already open, not opened after mount), Element Plus's el-dialog @open handler never fires for that first appearance — @open only fires on a transition from closed to open on an already-mounted dialog. components/sharing/ShareDialog.vue hit this and fixed it with a modelValue watcher instead:

ts
// Parents mount this dialog with v-if AND open it in the same tick, so
// el-dialog's @open never fires on that initial already-open mount. Load
// on modelValue instead (immediate covers the mount-while-open case,
// reloads on each subsequent open).
watch(() => props.modelValue, (open) => { if (open) void onOpen(); }, { immediate: true });

Use this pattern (watch + immediate: true on the v-model prop, not @open) for any dialog whose load-on-open logic must run reliably regardless of how the parent mounts it.

Sharing UI rollout is unfinished — three connection views still need it

Group-based resource sharing (ShareDialog.vue + SharedBadge.vue + useResourceSharing() composable — isOwned/isShared/canManage/ canShare/canManageGrants) is wired into SecretsView, DatabaseView, and several catalog detail panels — but not yet into CloudConnectionView, KafkaConnectionView, or GoogleAnalyticsConnectionView (verified as of 2026-07-03: none of the three import ShareDialog/SharedBadge/useResourceSharing). If asked to finish this rollout, copy the exact pattern from DatabaseView.vue — a SharedBadge :access="connection.access" on each list item, a ShareDialog v-model="showShareDialog" — and remember two places must add the field, not one:

  1. The connection's TS type needs id: number and access?: AccessInfo | null added (see types/secrets.types.ts for the reference shape).
  2. Its hand-written api.ts mapper needs the same fields added explicitly. These mappers enumerate fields one by one when converting backend JSON to the frontend type — an unlisted field is silently dropped, so adding it only to the TS type produces a field that type-checks but is always undefined at runtime. (The catalog store/api layer is a verbatim pass-through, which is why catalog types only needed the type-level change — connection views are not that shape.)

12. Lint, format, and test commands

bash
cd flowfile_frontend
npm run lint            # eslint --fix ./src/**/*.{ts,vue} — legacy eslintrc (eslint 8), not the root flat config
npm run build:web       # lint + vue-tsc --noEmit + vite build -> build/renderer/
npm run test:unit       # vitest run, node env, picks up only src/**/*.test.ts — co-locate new unit tests
npm run test:web        # playwright tests/web-flow.spec.ts (needs core :63578 + a web server already running)

ESLint config is .eslintrc.js (legacy format), not the repo's root flat config — vue/multi-word-component-names and no-explicit-any are off, prettier/prettier runs at warn. Prettier: double quotes, 2-space tabs, 100-char width, trailing commas everywhere, LF line endings (.prettierrc.json).

flowfile_wasm has its own toolchain (npm run test / test:run under flowfile_wasm/, Vitest with happy-dom + fake-indexeddb, plus a CPython pytest suite for the shared engine package and a real-Pyodide smoke test) — see flowfile_wasm/CLAUDE.md for the full command set; it is not duplicated here.


Provenance and maintenance

All facts above were verified by reading source at commit f6963c77 (branch feature/claude-skills), app/frontend version 0.12.7, dated 2026-07-03. Line numbers and counts drift fastest — re-run these before trusting a specific number:

bash
# --- path aliases stay in sync across the three config files ---
grep -n "['\"]@['\"]" flowfile_frontend/vite.config.mjs flowfile_frontend/vitest.config.ts
grep -n '"@/\*"' flowfile_frontend/tsconfig.json

# --- trailing-slash route pairs (frontend path == backend decorator) ---
grep -n "update_settings\|node/description\|node/reference" flowfile_frontend/src/renderer/app/api/node.api.ts
grep -n '@router.post("/update_settings/")\|@router.post("/node/description/")\|@router.post("/node/reference/")' \
  flowfile_core/flowfile_core/routes/routes.py

# --- node-settings element tree size + dead useNodes.ts glob target ---
find flowfile_frontend/src/renderer/app/components/nodes/node-types/elements/ -name "*.vue" | wc -l
find flowfile_frontend/src/renderer/app/features/designer/nodes -maxdepth 0 2>/dev/null; echo "exit=$?"  # nonzero/empty = still missing

# --- Tauri generate_handler! set + capabilities ---
grep -n "generate_handler" flowfile_frontend/src-tauri/src/lib.rs
cat flowfile_frontend/src-tauri/capabilities/main.json

# --- god-component TODO(refactor) file count + full current list ---
grep -rl "TODO(refactor)" flowfile_frontend/src/ flowfile_frontend/src-tauri/ | grep -v node_modules

# --- sharing UI rollout: which connection views still lack ShareDialog? ---
for f in CloudConnectionView KafkaConnectionView GoogleAnalyticsConnectionView DatabaseView; do
  echo "=== $f ==="; grep -rn "ShareDialog\|SharedBadge\|useResourceSharing" \
    flowfile_frontend/src/renderer/app/views/$f*/*.vue 2>/dev/null
done

# --- ShareDialog @open workaround + StatsPanel forwarding still present ---
grep -n "already-open\|modelValue.*immediate" flowfile_frontend/src/renderer/app/components/sharing/ShareDialog.vue
grep -n "defineEmits" flowfile_frontend/src/renderer/app/views/CatalogView/StatsPanel.vue

# --- wasm node/category count (source of truth: nodeCatalog.ts) + pyodide pin ---
grep -c "available: false" flowfile_wasm/src/config/nodeCatalog.ts
grep -n "pyodide.js\|indexURL" flowfile_wasm/src/stores/pyodide-store.ts

# --- versions ---
grep -n '"version"' flowfile_frontend/package.json flowfile_frontend/src-tauri/tauri.conf.json flowfile_wasm/package.json

© Edwardvaneechoud, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .claude/skills/flowfile-frontend-conventions of Edwardvaneechoud/Flowfile.

Open the folder on GitHubat commit 13aa287

Compare with similar skills

Flowfile Frontend Conventions next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Flowfile Frontend Conventions compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Flowfile Frontend Conventions this skillEdwardvaneechoud/Flowfile375—~8.5kAutomated safety check: PassMIT
Calcpad Web Frontend Developerimartincei/CalcpadCE110—~1.1kAutomated safety check: NotesMIT
Cross Platform Tauri UIifer47/markeron1.2k—~2kAutomated safety check: PassMIT
Tauri Config Ipcifer47/markeron1.2k—~1kAutomated safety check: PassMIT
Vue TuiSimon-He95/vue-tui259—~1.2kAutomated safety check: PassMIT
Tv Remote UIventic/ventic174—~4.2kAutomated safety check: PassMIT

Similar skills

  • Calcpad Web Frontend Developer

    imartincei/CalcpadCE

    Expert developer for Calcpad.Web/frontend - the TypeScript/Vue 3 frontend monorepo.

    110 GitHub stars~1.1k tokensUpdated yesterday
    Frontend & DesignAuto-check: notes
  • Cross-platform UI styling for MarkerOn Tauri app (macOS WKWebView vs Windows WebView2).

    1.2k GitHub stars~2k tokensUpdated 2 days ago
    Frontend & DesignAuto-check passed
  • Tauri Config Ipc

    ifer47/markeron

    Extend MarkerOn settings, persisted config, or Tauri IPC commands.

    1.2k GitHub stars~1k tokensUpdated 2 days ago
    Frontend & DesignAuto-check passed
  • Vue Tui

    Simon-He95/vue-tui

    Terminal Vue UI development and review for @simonhe/vue-tui.

    259 GitHub stars~1.2k tokensUpdated yesterday
    DevelopmentAuto-check passed
  • Tv Remote UI

    ventic/ventic

    How this app stays usable from a TV remote (Android TV / Google TV).

    174 GitHub stars~4.2k tokensUpdated 5 days ago
    Frontend & DesignAuto-check passed
  • Rgsm Gui Debug

    mcthesw/game-save-manager

    Debug the RGSM frontend against the real Rust backend in a normal browser, without Tauri IPC or WebView tooling.

    1.1k GitHub stars~267 tokensUpdated 2 days ago
    Frontend & DesignAuto-check passed

More from Edwardvaneechoud/Flowfile

All 19 skills in this repo
  • Flowfile AI Subsystem Guide

    Edwardvaneechoud/Flowfile

    Maps the /ai/ subsystem of flowfile_core, its three agent tiers, litellm seam, BYOK keys and rate limits, and sets rules for extending or debugging it safely.

    375 GitHub stars~7k tokensUpdated today
    Auto-check: notes
  • Flowfile Architecture Contract

    Edwardvaneechoud/Flowfile

    Maps Flowfile's core, worker, frontend, kernel, scheduler and shared services and the design contracts between them, for onboarding and cross-service debugging.

    375 GitHub stars~9.9k tokensUpdated today
    Auto-check passed
  • Flowfile Build and Environment Setup

    Edwardvaneechoud/Flowfile

    Recreates every Flowfile development and build environment from scratch, with exact version pins and an explanation of what each Makefile target really does.

    375 GitHub stars~7.3k tokensUpdated today
    Auto-check: notes
  • Flowfile Change Control

    Edwardvaneechoud/Flowfile

    Explains how changes to the Flowfile monorepo are gated, versioned and released, including version sync, stub and docs drift checks, Alembic migrations and pinned dependencies.

    375 GitHub stars~7.3k tokensUpdated today
    Auto-check passed
  • Flowfile Codegen Parity Campaign

    Edwardvaneechoud/Flowfile

    Runbook for closing gaps between a Flowfile visual flow's results and its exported Polars or FlowFrame Python code, measured by tests rather than by eye.

    375 GitHub stars~7.5k tokensUpdated today
    Auto-check passed
  • Flowfile Config and Flags Catalog

    Edwardvaneechoud/Flowfile

    Catalog of Flowfile's environment variables and runtime flags: what each does, where the code reads it, its default, and where the docs disagree with the code.

    375 GitHub stars~12k tokensUpdated today
    Auto-check: notes

Questions about Flowfile Frontend Conventions

What does Flowfile Frontend Conventions do?

Vue 3 renderer + Tauri 2 shell conventions for flowfilefrontend and flowfilewasm — path aliases, Pinia store map, the axios trailing-slash 307 trap, the node-settings-by-glob-convention resolution…. Flowfile Frontend Conventions is an agent skill from Edwardvaneechoud/Flowfile.ts as the sole Tauri boundary, the sidecar boot/readiness/shutdown ladder, the 19-file god-component TODO(refactor) policy, and WASM's explicit-run-only rule.

When should I use Flowfile Frontend Conventions?

Flowfile Frontend Conventions fits situations like: editing a Vue component/view/store/route in flowfilefrontend; building a new nodes settings UI; touching axios API wrappers; seeing unexplained 307s.

How do I install Flowfile Frontend Conventions in Claude Code?

Run `npx skills add Edwardvaneechoud/Flowfile --skill flowfile-frontend-conventions -a claude-code`. Or copy the skill folder (.claude/skills/flowfile-frontend-conventions in Edwardvaneechoud/Flowfile) into .claude/skills/flowfile-frontend-conventions in your project. Claude Code loads it when a task matches its description.

How do I install Flowfile Frontend Conventions in Codex?

Run `npx skills add Edwardvaneechoud/Flowfile --skill flowfile-frontend-conventions -a codex`. Or copy the skill folder (.claude/skills/flowfile-frontend-conventions in Edwardvaneechoud/Flowfile) into .agents/skills/flowfile-frontend-conventions in your project. Codex loads it when a task matches its description.

Can I use Flowfile Frontend Conventions in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add Edwardvaneechoud/Flowfile --skill flowfile-frontend-conventions -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/flowfile-frontend-conventions, .gemini/skills/flowfile-frontend-conventions, .github/skills/flowfile-frontend-conventions and .opencode/skills/flowfile-frontend-conventions in your project.

What does Flowfile Frontend Conventions need to run?

Going by SKILL.md and its folder, Flowfile Frontend Conventions needs the command-line tools its instructions call (npm, docker and make). Our summary lists: Python 3; Docker.

Does Flowfile Frontend Conventions access the network?

SKILL.md contains no URLs. Its commands use npm and docker, which can reach the network depending on how they are called. This is read from the text; nothing was executed.

Is Flowfile Frontend Conventions safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Flowfile Frontend Conventions use?

Flowfile Frontend Conventions is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Flowfile Frontend Conventions use?

About 8.5k tokens (SKILL.md is roughly 34k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Flowfile Frontend Conventions?

Skills that share tags, products or a category with Flowfile Frontend Conventions: Calcpad Web Frontend Developer (imartincei/CalcpadCE, 110 stars), Cross Platform Tauri UI (ifer47/markeron, 1.2k stars), Tauri Config Ipc (ifer47/markeron, 1.2k stars) and Vue Tui (Simon-He95/vue-tui, 259 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Flowfile Frontend Conventions?

Edwardvaneechoud (a GitHub user) maintains it in Edwardvaneechoud/Flowfile, which has 375 GitHub stars. The repository holds 19 skills in this directory. The repository was last updated on October 9, 2026.

Source: Edwardvaneechoud/Flowfile on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.