Calcpad Web Frontend Developer
imartincei/CalcpadCE
Expert developer for Calcpad.Web/frontend - the TypeScript/Vue 3 frontend monorepo.
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…
$ npx skills add Edwardvaneechoud/Flowfile --skill flowfile-frontend-conventions -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install Edwardvaneechoud/Flowfile flowfile-frontend-conventions --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ 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-srcUse ~/.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/
Install the "flowfile-frontend-conventions" agent skill from https://github.com/Edwardvaneechoud/Flowfile/tree/main/.claude/skills/flowfile-frontend-conventions into .claude/skills/flowfile-frontend-conventions/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "flowfile-frontend-conventions", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/Edwardvaneechoud/Flowfile/tree/main/.claude/skills/flowfile-frontend-conventionsType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add Edwardvaneechoud/Flowfile --skill flowfile-frontend-conventions -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install Edwardvaneechoud/Flowfile flowfile-frontend-conventions --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Edwardvaneechoud/Flowfile.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/flowfile-frontend-conventions .agents/skills/flowfile-frontend-conventions && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "flowfile-frontend-conventions" agent skill from https://github.com/Edwardvaneechoud/Flowfile/tree/main/.claude/skills/flowfile-frontend-conventions into .agents/skills/flowfile-frontend-conventions/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "flowfile-frontend-conventions", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add Edwardvaneechoud/Flowfile --skill flowfile-frontend-conventions -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install Edwardvaneechoud/Flowfile flowfile-frontend-conventions --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Edwardvaneechoud/Flowfile.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/flowfile-frontend-conventions .cursor/skills/flowfile-frontend-conventions && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "flowfile-frontend-conventions" agent skill from https://github.com/Edwardvaneechoud/Flowfile/tree/main/.claude/skills/flowfile-frontend-conventions into .cursor/skills/flowfile-frontend-conventions/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "flowfile-frontend-conventions", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/Edwardvaneechoud/Flowfile.git --path .claude/skills/flowfile-frontend-conventions--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add Edwardvaneechoud/Flowfile --skill flowfile-frontend-conventions -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install Edwardvaneechoud/Flowfile flowfile-frontend-conventions --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Edwardvaneechoud/Flowfile.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/flowfile-frontend-conventions .gemini/skills/flowfile-frontend-conventions && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "flowfile-frontend-conventions" agent skill from https://github.com/Edwardvaneechoud/Flowfile/tree/main/.claude/skills/flowfile-frontend-conventions into .gemini/skills/flowfile-frontend-conventions/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "flowfile-frontend-conventions", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install Edwardvaneechoud/Flowfile flowfile-frontend-conventionsInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add Edwardvaneechoud/Flowfile --skill flowfile-frontend-conventions -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/Edwardvaneechoud/Flowfile.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/flowfile-frontend-conventions .github/skills/flowfile-frontend-conventions && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "flowfile-frontend-conventions" agent skill from https://github.com/Edwardvaneechoud/Flowfile/tree/main/.claude/skills/flowfile-frontend-conventions into .github/skills/flowfile-frontend-conventions/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "flowfile-frontend-conventions", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add Edwardvaneechoud/Flowfile --skill flowfile-frontend-conventions -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install Edwardvaneechoud/Flowfile flowfile-frontend-conventions --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Edwardvaneechoud/Flowfile.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/flowfile-frontend-conventions .opencode/skills/flowfile-frontend-conventions && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "flowfile-frontend-conventions" agent skill from https://github.com/Edwardvaneechoud/Flowfile/tree/main/.claude/skills/flowfile-frontend-conventions into .opencode/skills/flowfile-frontend-conventions/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "flowfile-frontend-conventions", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
flowfile-frontend-conventionsVue 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. 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.
12 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 13aa287. It shows what the files ask for, not the result of running them.
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.
Shell commands in SKILL.md call:
npmdockermakeFrom the folder's file list and the shell code blocks in SKILL.md.
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.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
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.
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.
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.
The full file from Edwardvaneechoud/Flowfile at commit 13aa287, republished under its MIT licence (© Edwardvaneechoud). 3,500 words, ~8,531 tokens.
.claude/skills/flowfile-frontend-conventions/SKILL.md (or your agent's skills folder).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.
flowfile-architecture-contract.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.flowfile-ai-subsystem.flowfile-build-and-env / flowfile-run-and-operate.flowfile-config-and-flags.flowfile-testing-and-validation.flowfile-debugging-playbook.flowfile-failure-archaeology.flowfile-change-control — nothing here overrides it.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):
| File | Aliases 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.ts | only 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.
Files live in app/stores/, kebab-case xxx-store.ts (one legacy exception:
fileBrowserStore.ts). index.ts is the Pinia plugin.
| Store (id) | Role |
|---|---|
flow | flowId (persisted in sessionStorage['last_flow_id']), the live vueFlowInstance, undo/redo historyState, and two monotonic signal counters (see below). |
node | current nodeId, nodeData cache, updateSettings (the POST /update_settings/ path — see §4), many @deprecated proxy getters to flow/editor/results for legacy callers. |
editor | drawer open/active-component state, shared code-editor buffer, graphVersion (dirty-state counter), plus "request token" signals like nodeSettingsOpenRequest {nodeId, token}. |
results | run results per flow/node, resultVersion. |
auth, theme | user/session; light/dark/system (localStorage flowfile-theme-preference). |
column | deprecated shim — re-exports useNodeStore; Canvas.vue still imports it under this name for back-compat. Don't add new state here. |
| Feature stores | fileBrowserStore, 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 stores | ai-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."
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):
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.
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.
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:
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.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_<type>) is
flowfile-node-development's job — do that first.<item>.svg into app/features/designer/assets/icons/.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.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).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).@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).
data carries {id, label, component (markRaw), inputs/outputs (NodeHandle[]), nodeTemplate}.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.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.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.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.lib/desktop.ts — the sole Tauri boundaryRule (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__.
| Method | Desktop | Web 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 code | window.location.assign(url) |
openExternal(url) | invoke("plugin:opener|open_url") — system browser | window.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 menu | no-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.
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).
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:
ps aux | grep -i flowfile | grep -v grep # expect: no flowfile_core-*/flowfile_worker-* rowsA 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:
// 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 composableThe rule when you touch one of these files:
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.| Concern | Web (vite dev / Docker nginx) | Desktop (Tauri) |
|---|---|---|
| API base | <origin>/api/ (proxied, replicates 307 rewrite) | http://127.0.0.1:<injected port>/ direct |
| Detection | isDesktop false | window.__TAURI_INTERNALS__ present |
| Auth | full login flow, unauth → /login | FLOWFILE_MODE=electron auto-issues tokens, no login redirect |
| Ports | fixed 63578 | scanned pair; multiple app instances can coexist |
| Clipboard read | navigator.clipboard (browser permission prompt) | clipboard-manager plugin (no macOS pill) |
| External links / OAuth | window.open / location.assign | opener plugin (system browser) / modal oauth window |
| App version | Vite __APP_VERSION__ define | get_app_version command |
/project, /user-groups, /shares routers | gated by FLOWFILE_MODE/FLOWFILE_ENABLE_PROJECTS server-side (404 in electron for the sharing routers) | projects always on in electron; sharing routers still 404 |
flowfile_wasm hard rulesSeparate npm package flowfile-editor, pure Vue 3 + VueFlow + Pyodide-in-
browser — no backend, no axios; nothing in §1-§9 above applies here.
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.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.loadPackage(['polars', 'pydantic']).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-wasm converts
Parquet↔IPC via a deliberately bundler-opaque dynamic import so
webpack5/esbuild don't try to statically resolve a literal https:// URL.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).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.
@open does not fire for a dialog mounted already-openIf 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:
// 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.
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:
id: number and access?: AccessInfo | null added (see types/secrets.types.ts for the reference shape).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.)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.
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:
# --- 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
Just SKILL.md in .claude/skills/flowfile-frontend-conventions of Edwardvaneechoud/Flowfile.
Open the folder on GitHubat commit 13aa287
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.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Flowfile Frontend Conventions this skillEdwardvaneechoud/Flowfile | 375 | — | ~8.5k | Automated safety check: Pass | MIT | |
| Calcpad Web Frontend Developerimartincei/CalcpadCE | 110 | — | ~1.1k | Automated safety check: Notes | MIT | |
| Cross Platform Tauri UIifer47/markeron | 1.2k | — | ~2k | Automated safety check: Pass | MIT | |
| Tauri Config Ipcifer47/markeron | 1.2k | — | ~1k | Automated safety check: Pass | MIT | |
| Vue TuiSimon-He95/vue-tui | 259 | — | ~1.2k | Automated safety check: Pass | MIT | |
| Tv Remote UIventic/ventic | 174 | — | ~4.2k | Automated safety check: Pass | MIT |
imartincei/CalcpadCE
Expert developer for Calcpad.Web/frontend - the TypeScript/Vue 3 frontend monorepo.
ifer47/markeron
Cross-platform UI styling for MarkerOn Tauri app (macOS WKWebView vs Windows WebView2).
ifer47/markeron
Extend MarkerOn settings, persisted config, or Tauri IPC commands.
Simon-He95/vue-tui
Terminal Vue UI development and review for @simonhe/vue-tui.
ventic/ventic
How this app stays usable from a TV remote (Android TV / Google TV).
mcthesw/game-save-manager
Debug the RGSM frontend against the real Rust backend in a normal browser, without Tauri IPC or WebView tooling.
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.
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.
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.
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.
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.
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.
Works with
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.