Compare commits
3 Commits
glm/free-f
...
removeplug
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90782336c8 | ||
|
|
b121f4388b | ||
|
|
5ebaa43aa1 |
@@ -110,7 +110,6 @@
|
||||
]
|
||||
},
|
||||
"enabledPlugins": {
|
||||
"rust-analyzer-lsp@claude-plugins-official": true,
|
||||
"typescript-lsp@claude-plugins-official": true,
|
||||
"code-review@claude-plugins-official": true
|
||||
}
|
||||
|
||||
@@ -99,6 +99,8 @@ profiles:
|
||||
|
||||
5) Include in PR descriptions using markdown image syntax.
|
||||
|
||||
IMPORTANT: Read docs/autonomous-mode.md before starting any work.
|
||||
|
||||
linkedRepos:
|
||||
- repo: windmill-labs/windmill-ee-private
|
||||
alias: ee
|
||||
|
||||
@@ -55,7 +55,8 @@ panes:
|
||||
- Pane 2: frontend (npm run dev)\n\n
|
||||
To check logs, use: \`tmux capture-pane -t .1 -p -S -50\` (backend) or \`tmux capture-pane -t .2 -p -S -50\` (frontend).\n
|
||||
When restarting backend or frontend, make sure to use the ports listed in .env.local.\n
|
||||
Because we are running backend with cargo watch, to verify your changes, just check the logs in the backend pane. No need for cargo check."
|
||||
Because we are running backend with cargo watch, to verify your changes, just check the logs in the backend pane. No need for cargo check.\n\n
|
||||
IMPORTANT: Read docs/autonomous-mode.md before starting any work."
|
||||
focus: true
|
||||
- command: 'ROOT="$(git rev-parse --show-toplevel)"; [ -f "$ROOT/.env.local" ] && source "$ROOT/.env.local"; cd "$ROOT/backend" && PORT=${BACKEND_PORT:-8000} cargo watch -x "run ${CARGO_FEATURES:+--features $CARGO_FEATURES}"'
|
||||
split: horizontal
|
||||
|
||||
@@ -12,7 +12,6 @@ Open-source platform for internal tools, workflows, API integrations, background
|
||||
## Documentation
|
||||
|
||||
- **Validation**: `docs/validation.md` — what checks to run based on what you changed
|
||||
- **Autonomous mode**: `docs/autonomous-mode.md` — when running in bypass/auto permission mode
|
||||
- **Enterprise**: `docs/enterprise.md` — EE file conventions and PR workflow
|
||||
- **Backend patterns**: use the `rust-backend` skill when writing Rust code
|
||||
- **Frontend patterns**: use the `svelte-frontend` skill when writing Svelte code
|
||||
|
||||
@@ -39,26 +39,6 @@ Even in bypass mode, **enter plan mode before starting non-trivial work**. Ask a
|
||||
- Read `docs/validation.md` to know what checks you'll need to run
|
||||
- Break large features into stages — commit each stage separately
|
||||
|
||||
## Dev Environment (tmux)
|
||||
|
||||
Backend and frontend are running in tmux panes in the current session:
|
||||
|
||||
- **Pane 0**: This pane (Claude agent)
|
||||
- **Pane 1**: Backend (`cargo watch -x run`)
|
||||
- **Pane 2**: Frontend (`npm run dev`)
|
||||
|
||||
### Checking Logs
|
||||
|
||||
```bash
|
||||
# Backend logs (last 50 lines)
|
||||
tmux capture-pane -t .1 -p -S -50
|
||||
|
||||
# Frontend logs (last 50 lines)
|
||||
tmux capture-pane -t .2 -p -S -50
|
||||
```
|
||||
|
||||
Backend runs with `cargo watch`, so Rust changes auto-recompile — just check pane 1 logs for errors. No need to run `cargo check` separately during iteration if the backend pane shows a successful build.
|
||||
|
||||
## Manual Testing
|
||||
|
||||
After code changes compile and type-check, verify the feature works:
|
||||
|
||||
@@ -13,9 +13,6 @@ export async function initFlow(
|
||||
flowStateStore: StateStore<FlowState>
|
||||
) {
|
||||
await initFlowState(flow, flowStateStore)
|
||||
if (flow.value && !flow.value.node_offsets) {
|
||||
flow.value.node_offsets = {}
|
||||
}
|
||||
flowStore.val = flow
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,6 @@
|
||||
const flowGraphContext = getGraphContext()
|
||||
const diffManager = flowGraphContext?.diffManager
|
||||
const moveManager = flowGraphContext?.moveManager
|
||||
const freeDrag = flowGraphContext?.freeDrag
|
||||
|
||||
let pickableIds: Record<string, any> | undefined = $state(undefined)
|
||||
|
||||
@@ -322,13 +321,7 @@
|
||||
style="width: 275px; height: 34px;"
|
||||
onmouseenter={() => (hover = true)}
|
||||
onmouseleave={() => (hover = false)}
|
||||
onpointerdown={(e) => {
|
||||
if (!$freeDrag) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
dispatch('pointerdown', e)
|
||||
}}
|
||||
onpointerdown={stopPropagation(preventDefault((e) => dispatch('pointerdown', e)))}
|
||||
>
|
||||
{#if id}
|
||||
<DiffActionBar moduleId={id} {moduleAction} {diffManager} {flowStore} />
|
||||
|
||||
@@ -465,18 +465,6 @@
|
||||
modules={flowStore.val.value.modules}
|
||||
{noteMode}
|
||||
notes={flowStore.val.value.notes}
|
||||
nodeOffsets={flowStore.val.value.node_offsets}
|
||||
onNodeOffsetUpdate={(nodeId, offset) => {
|
||||
if (!flowStore.val.value.node_offsets) {
|
||||
flowStore.val.value.node_offsets = {}
|
||||
}
|
||||
flowStore.val.value.node_offsets[nodeId] = offset
|
||||
refreshStateStore(flowStore)
|
||||
}}
|
||||
onNodeOffsetsReset={() => {
|
||||
flowStore.val.value.node_offsets = {}
|
||||
refreshStateStore(flowStore)
|
||||
}}
|
||||
preprocessorModule={flowStore.val.value?.preprocessor_module}
|
||||
failureModule={flowStore.val.value?.failure_module}
|
||||
currentInputSchema={flowStore.val.schema}
|
||||
|
||||
@@ -76,7 +76,7 @@ export function filteredContentForExport(flow: ExtendedOpenFlow) {
|
||||
let o = {
|
||||
summary: flow.summary,
|
||||
description: flow.description,
|
||||
value: $state.snapshot(flow.value),
|
||||
value: flow.value,
|
||||
schema: flow.schema
|
||||
}
|
||||
if (flow.dedicated_worker) {
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
import BaseEdge from './renderers/edges/BaseEdge.svelte'
|
||||
import EmptyEdge from './renderers/edges/EmptyEdge.svelte'
|
||||
import { sugiyama, dagStratify, coordCenter, decrossTwoLayer, decrossOpt } from 'd3-dag'
|
||||
import { Expand, MousePointer, Hand, RotateCcw } from 'lucide-svelte'
|
||||
import { Expand, MousePointer, Hand } from 'lucide-svelte'
|
||||
import Toggle from '../Toggle.svelte'
|
||||
import DataflowEdge from './renderers/edges/DataflowEdge.svelte'
|
||||
import { encodeState, readFieldsRecursively, getModifierKey, isMac } from '$lib/utils'
|
||||
@@ -81,7 +81,6 @@
|
||||
|
||||
let useDataflow: Writable<boolean | undefined> = writable<boolean | undefined>(false)
|
||||
let showAssets: Writable<boolean | undefined> = writable<boolean | undefined>(true)
|
||||
let freeDrag: Writable<boolean> = writable<boolean>(false)
|
||||
let showNotes = $state(true)
|
||||
|
||||
const triggerContext = getContext<TriggerContext>('TriggerContext')
|
||||
@@ -130,9 +129,6 @@
|
||||
suspendStatus?: Record<string, { job: Job; nb: number }>
|
||||
noteMode?: boolean
|
||||
notes?: FlowNote[]
|
||||
nodeOffsets?: Record<string, { x: number; y: number }>
|
||||
onNodeOffsetUpdate?: (nodeId: string, offset: { x: number; y: number }) => void
|
||||
onNodeOffsetsReset?: () => void
|
||||
chatInputEnabled?: boolean
|
||||
multiSelectEnabled?: boolean
|
||||
onDelete?: (id: string) => void
|
||||
@@ -226,9 +222,6 @@
|
||||
flowHasChanged = false,
|
||||
noteMode = false,
|
||||
notes = undefined,
|
||||
nodeOffsets = undefined,
|
||||
onNodeOffsetUpdate = undefined,
|
||||
onNodeOffsetsReset = undefined,
|
||||
exitNoteMode = undefined,
|
||||
onNotePositionUpdate = undefined,
|
||||
chatInputEnabled = false,
|
||||
@@ -287,7 +280,6 @@
|
||||
selectionManager: selectionManager,
|
||||
useDataflow,
|
||||
showAssets,
|
||||
freeDrag,
|
||||
noteManager,
|
||||
moveManager,
|
||||
clearFlowSelection,
|
||||
@@ -522,26 +514,6 @@
|
||||
let nodes = $state.raw<Node[]>([])
|
||||
let edges = $state.raw<Edge[]>([])
|
||||
|
||||
// Track layout-computed positions (before user offsets) for drag offset computation
|
||||
let layoutPositions: Record<string, { x: number; y: number }> = {}
|
||||
|
||||
const DRAGGABLE_NODE_TYPES = new Set([
|
||||
'module',
|
||||
'forLoopStart',
|
||||
'forLoopEnd',
|
||||
'branchAllStart',
|
||||
'branchAllEnd',
|
||||
'branchOneStart',
|
||||
'branchOneEnd',
|
||||
'whileLoopStart',
|
||||
'whileLoopEnd',
|
||||
'noBranch',
|
||||
'subflowBound',
|
||||
'input2',
|
||||
'result',
|
||||
'trigger'
|
||||
])
|
||||
|
||||
let height = $state(0)
|
||||
|
||||
// Derived nodes with yOffset applied to all nodes uniformly and selectable flag set to false if notSelectable is true
|
||||
@@ -670,28 +642,6 @@
|
||||
}))
|
||||
}
|
||||
|
||||
// Capture layout positions (after all spacing, before user offsets)
|
||||
for (const n of finalNodes) {
|
||||
if (n.type && DRAGGABLE_NODE_TYPES.has(n.type)) {
|
||||
layoutPositions[n.id] = { x: n.position.x, y: n.position.y }
|
||||
}
|
||||
}
|
||||
|
||||
// Apply user-defined node offsets and set draggable flag
|
||||
if (nodeOffsets || editMode) {
|
||||
finalNodes = finalNodes.map((n) => {
|
||||
const isDraggable = n.type != null && DRAGGABLE_NODE_TYPES.has(n.type)
|
||||
const offset = nodeOffsets?.[n.id]
|
||||
return {
|
||||
...n,
|
||||
position: offset
|
||||
? { x: n.position.x + offset.x, y: n.position.y + offset.y }
|
||||
: n.position,
|
||||
draggable: isDraggable && editMode
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// update nodes
|
||||
nodes = [...finalNodes, ...(noteNodesResult?.noteNodes ?? [])]
|
||||
|
||||
@@ -814,10 +764,9 @@
|
||||
$showAssets && Object.values(nodes).every((n) => n.type !== 'asset')
|
||||
)
|
||||
let hideNotesToggle = $derived(!notes || notes.length === 0)
|
||||
let hasNodeOffsets = $derived(nodeOffsets && Object.keys(nodeOffsets).length > 0)
|
||||
|
||||
$effect(() => {
|
||||
;[graph, allowSimplifiedPoll, $showAssets, showNotes, noteManager.renderCount, nodeOffsets]
|
||||
;[graph, allowSimplifiedPoll, $showAssets, showNotes, noteManager.renderCount]
|
||||
untrack(async () => {
|
||||
await updateStores()
|
||||
})
|
||||
@@ -985,23 +934,13 @@
|
||||
paneContextMenu?.onPaneContextMenu(event)
|
||||
}}
|
||||
onnodedragstop={(event) => {
|
||||
for (const node of event.nodes) {
|
||||
if (node.type === 'note') {
|
||||
const positionWithOffset = {
|
||||
x: node.position.x,
|
||||
y: node.position.y - yOffset
|
||||
}
|
||||
onNotePositionUpdate?.(node.id, positionWithOffset)
|
||||
} else if (node.type && DRAGGABLE_NODE_TYPES.has(node.type)) {
|
||||
const layoutPos = layoutPositions[node.id]
|
||||
if (layoutPos && onNodeOffsetUpdate) {
|
||||
const offset = {
|
||||
x: node.position.x - layoutPos.x,
|
||||
y: node.position.y - yOffset - layoutPos.y
|
||||
}
|
||||
onNodeOffsetUpdate(node.id, offset)
|
||||
}
|
||||
const node = event.targetNode
|
||||
if (node && node.type === 'note') {
|
||||
const positionWithOffset = {
|
||||
x: node.position.x,
|
||||
y: node.position.y - yOffset
|
||||
}
|
||||
onNotePositionUpdate?.(node.id, positionWithOffset)
|
||||
}
|
||||
}}
|
||||
onmove={(event, viewport) => {
|
||||
@@ -1028,7 +967,7 @@
|
||||
elevateNodesOnSelect={false}
|
||||
{proOptions}
|
||||
multiSelectionKey={'Shift'}
|
||||
nodesDraggable={$freeDrag || false}
|
||||
nodesDraggable={false}
|
||||
--background-color={false}
|
||||
>
|
||||
<div class="absolute inset-0 !bg-surface-secondary h-full" id="flow-graph-v2"></div>
|
||||
@@ -1128,18 +1067,6 @@
|
||||
{#if showDataflow}
|
||||
<Toggle bind:checked={$useDataflow} size="xs" options={{ right: 'Dataflow' }} />
|
||||
{/if}
|
||||
{#if editMode}
|
||||
<Toggle bind:checked={$freeDrag} size="xs" options={{ right: 'Free drag' }} />
|
||||
{/if}
|
||||
{#if editMode && hasNodeOffsets}
|
||||
<button
|
||||
class="flex items-center gap-1 text-xs text-secondary hover:text-primary transition-colors"
|
||||
onclick={() => onNodeOffsetsReset?.()}
|
||||
>
|
||||
<RotateCcw size={12} />
|
||||
Reset layout
|
||||
</button>
|
||||
{/if}
|
||||
</Controls>
|
||||
{/if}
|
||||
</SvelteFlow>
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
selectionManager: new SelectionManager(),
|
||||
useDataflow: writable(false),
|
||||
showAssets: writable(false),
|
||||
freeDrag: writable(false),
|
||||
diffManager: createFlowDiffManager()
|
||||
})
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ export type GraphContext = {
|
||||
selectionManager: SelectionManager
|
||||
useDataflow: Writable<boolean | undefined>
|
||||
showAssets: Writable<boolean | undefined>
|
||||
freeDrag: Writable<boolean>
|
||||
noteManager?: NoteManager
|
||||
moveManager?: MoveManager
|
||||
clearFlowSelection?: () => void
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
const flowStore: StateStore<Flow> = $state({
|
||||
val: {
|
||||
summary: '',
|
||||
value: { modules: [], node_offsets: {} },
|
||||
value: { modules: [] },
|
||||
path: '',
|
||||
edited_at: '',
|
||||
edited_by: '',
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
export const flowStore: StateStore<Flow> = $state({
|
||||
val: {
|
||||
summary: '',
|
||||
value: { modules: [], node_offsets: {} },
|
||||
value: { modules: [] },
|
||||
path: '',
|
||||
edited_at: '',
|
||||
edited_by: '',
|
||||
|
||||
@@ -113,17 +113,6 @@ components:
|
||||
description: Sticky notes attached to the flow
|
||||
items:
|
||||
$ref: '#/components/schemas/FlowNote'
|
||||
node_offsets:
|
||||
type: object
|
||||
description: User-defined position offsets for flow nodes, keyed by module ID
|
||||
additionalProperties:
|
||||
type: object
|
||||
properties:
|
||||
x:
|
||||
type: number
|
||||
y:
|
||||
type: number
|
||||
required: [x, y]
|
||||
required:
|
||||
- modules
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ SETTINGS
|
||||
# Extracts --features <value> from args, exports CARGO_FEATURES, returns remaining args.
|
||||
# Usage: parse_features_flag "$@"; set -- "${remaining_args[@]}"
|
||||
|
||||
parse_features_flag() {
|
||||
parse_flags() {
|
||||
remaining_args=()
|
||||
while (( $# )); do
|
||||
case $1 in
|
||||
@@ -127,6 +127,10 @@ parse_features_flag() {
|
||||
export CARGO_FEATURES=${1#--features=}
|
||||
shift
|
||||
;;
|
||||
--clone-db)
|
||||
export WM_CLONE_DB=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
remaining_args+=("$1")
|
||||
shift
|
||||
@@ -142,7 +146,7 @@ cmd_add() {
|
||||
ensure_tmux
|
||||
check_dev_db
|
||||
|
||||
parse_features_flag "$@"
|
||||
parse_flags "$@"
|
||||
set -- "${remaining_args[@]}"
|
||||
|
||||
# Snapshot worktree list before
|
||||
@@ -176,7 +180,7 @@ cmd_open() {
|
||||
ensure_tmux
|
||||
check_dev_db
|
||||
|
||||
parse_features_flag "$@"
|
||||
parse_flags "$@"
|
||||
set -- "${remaining_args[@]}"
|
||||
|
||||
# Write CARGO_FEATURES to .env.local if specified
|
||||
@@ -391,6 +395,7 @@ case ${1-} in
|
||||
print -u2 ""
|
||||
print -u2 "Options:"
|
||||
print -u2 " --features <features> Cargo features for the backend (e.g. \"enterprise,parquet\")"
|
||||
print -u2 " --clone-db Clone the main 'windmill' database instead of creating an empty one"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -61,9 +61,17 @@ if command -v psql &>/dev/null; then
|
||||
if psql "$db_conn/postgres" -tc "SELECT 1 FROM pg_database WHERE datname = '${db_name}'" 2>/dev/null | grep -q 1; then
|
||||
echo "Database $db_name already exists"
|
||||
else
|
||||
psql "$db_conn/postgres" -c "CREATE DATABASE ${db_name}" 2>/dev/null \
|
||||
&& echo "Created database $db_name" \
|
||||
|| echo "WARNING: Could not create database $db_name (is PostgreSQL running?)" >&2
|
||||
if [[ -n "${WM_CLONE_DB:-}" ]]; then
|
||||
# Terminate active connections so CREATE DATABASE ... TEMPLATE works
|
||||
psql "$db_conn/postgres" -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'windmill' AND pid <> pg_backend_pid();" 2>/dev/null || true
|
||||
psql "$db_conn/postgres" -c "CREATE DATABASE ${db_name} TEMPLATE windmill" 2>/dev/null \
|
||||
&& echo "Created database $db_name (template: windmill)" \
|
||||
|| echo "WARNING: Could not create database $db_name from template windmill" >&2
|
||||
else
|
||||
psql "$db_conn/postgres" -c "CREATE DATABASE ${db_name}" 2>/dev/null \
|
||||
&& echo "Created database $db_name" \
|
||||
|| echo "WARNING: Could not create database $db_name (is PostgreSQL running?)" >&2
|
||||
fi
|
||||
fi
|
||||
db_url="${db_conn}/${db_name}?sslmode=disable"
|
||||
# Run migrations against the new database
|
||||
|
||||
Reference in New Issue
Block a user