mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-22 00:02:38 +00:00
* feat: gate drafts on real user input so a moved-on schema is not a draft Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScVgqGpuyDMWdzVNPm7Q5f * Revert "feat: gate drafts on real user input so a moved-on schema is not a draft" This reverts commit6cd86cf727. * fix: stop counting empty schema-added fields and server metadata as drafts Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScVgqGpuyDMWdzVNPm7Q5f * feat: sweep away existing drafts that carry no changes, once per workspace Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScVgqGpuyDMWdzVNPm7Q5f * Reapply "feat: gate drafts on real user input so a moved-on schema is not a draft" This reverts commitb7b18e345e. * Revert "fix: stop counting empty schema-added fields and server metadata as drafts" This reverts commit9787270ad8. * docs: describe the sweep by the gate that now prevents new phantom drafts Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScVgqGpuyDMWdzVNPm7Q5f * fix: make the draft sweep a compare-and-delete so it cannot eat live edits Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScVgqGpuyDMWdzVNPm7Q5f * fix: close the gate's load-time window and stop sealing a failed sweep Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScVgqGpuyDMWdzVNPm7Q5f * fix: open the gate on the edit itself, and stop the sweep at ownerless drafts Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScVgqGpuyDMWdzVNPm7Q5f * fix: count a click as an edit, and keep an unjudged row from sealing the sweep Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScVgqGpuyDMWdzVNPm7Q5f * fix: release the sweep's sync baseline when its delete is refused Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScVgqGpuyDMWdzVNPm7Q5f * fix: drop the refused delete before re-baselining, and bound the sweep's retries Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScVgqGpuyDMWdzVNPm7Q5f * refactor: send the sweep's delete straight to the API, not through the syncer Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScVgqGpuyDMWdzVNPm7Q5f * fix: never absorb a change the resource type's schema could not have made Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScVgqGpuyDMWdzVNPm7Q5f * fix: push an edit the gate only notices after the write has landed Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScVgqGpuyDMWdzVNPm7Q5f * fix: stop the gating effect re-suspending a resource opened on a draft Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ScVgqGpuyDMWdzVNPm7Q5f * chore: update ee-repo-ref to d33ea730c550cdbc7d050aeb6d40dcef3d134e07 This commit updates the EE repository reference after PR #782 was merged in windmill-ee-private. Previous ee-repo-ref: 313c572c9dcbcaafd8a1594df4054f9dd26f395c New ee-repo-ref: d33ea730c550cdbc7d050aeb6d40dcef3d134e07 Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
67 lines
3.3 KiB
TypeScript
67 lines
3.3 KiB
TypeScript
import { onDestroy } from 'svelte'
|
|
|
|
/**
|
|
* What kind of event opened the gate.
|
|
*
|
|
* - `value`: the user changed something — the event *is* the edit.
|
|
* - `precursor`: a gesture that usually precedes an edit. Needed because a
|
|
* custom component (a picker, a toggle built out of divs) writes its value
|
|
* through Svelte state and fires no native value event at all, so waiting for
|
|
* one would drop those edits. The cost is that a bare click counts too.
|
|
*/
|
|
export type UserInputKind = 'value' | 'precursor'
|
|
|
|
/** Events that ARE an edit. `drop` and `paste` matter on their own: text
|
|
* dragged in from another application produces no pointer or key event in this
|
|
* document at all. */
|
|
const VALUE_EVENTS = ['input', 'change', 'drop', 'paste'] as const
|
|
/** `click` is here for the controls that mutate state from a click handler and
|
|
* fire no native value event — ArgInput's "Add item", say. A mouse always sends
|
|
* `pointerdown` first, but an assistive technology can activate one with a
|
|
* trusted `click` alone, and that is a real edit with nothing else to catch it. */
|
|
const PRECURSOR_EVENTS = ['pointerdown', 'keydown', 'click'] as const
|
|
|
|
/**
|
|
* A draft is supposed to record what the USER changed, but an editor built
|
|
* from a schema writes into the value on its own: the form materializes a
|
|
* property the stored item never carried (an empty string, `false`, the first
|
|
* option of a required enum, a schema `default`) and deletes one a `showExpr`
|
|
* hides. So merely opening an item whose schema has moved on makes it diverge
|
|
* from the deployed value with nobody having touched it — a draft nobody asked
|
|
* for, cluttering the workspace.
|
|
*
|
|
* An editor guards against that by gating its draft on this: nothing the form
|
|
* settles on counts until the user has actually put something in. Callers
|
|
* decide what a gate covers (the resource editor keys it by workspace, since
|
|
* switching workspaces re-renders the form against a fresh value) and what
|
|
* gating means for them — suspending the autosave, absorbing the settled value
|
|
* into the deployed baseline, or both.
|
|
*
|
|
* Capture phase puts this ahead of the handler that writes the value, so a gate
|
|
* opened here is already open by the time the edit lands. Listening on the
|
|
* document rather than the editor's own subtree is deliberate: pickers and
|
|
* modals render in portals outside it, and missing a real edit would silently
|
|
* drop the user's work, while opening the gate too eagerly only costs the
|
|
* phantom draft that existed before.
|
|
*
|
|
* Registers for the lifetime of the calling component — call it during init.
|
|
*/
|
|
export function onUserInput(handle: (kind: UserInputKind) => void): void {
|
|
if (typeof document === 'undefined') return
|
|
const listeners: Array<[string, (e: Event) => void]> = []
|
|
const register = (type: string, kind: UserInputKind) => {
|
|
const onEvent = (e: Event) => {
|
|
// A programmatic `dispatchEvent` is untrusted, which is what keeps the
|
|
// form's own settling from opening the gate it is gated by.
|
|
if (e.isTrusted) handle(kind)
|
|
}
|
|
document.addEventListener(type, onEvent, true)
|
|
listeners.push([type, onEvent])
|
|
}
|
|
for (const type of VALUE_EVENTS) register(type, 'value')
|
|
for (const type of PRECURSOR_EVENTS) register(type, 'precursor')
|
|
onDestroy(() => {
|
|
for (const [type, onEvent] of listeners) document.removeEventListener(type, onEvent, true)
|
|
})
|
|
}
|