Import already handled workflow bundles, but nothing told the UI. Unlike a
dashboard there is no route to navigate to, so the hub -- which only fetches on
mount -- kept showing a stale list and the imported workflow looked like it had
vanished until a remount.
Refetch on a workflow-root import. Import drops dashboard_id and /list keeps
unassigned workflows for every dashboard, so it shows up wherever the user is.
Puts Share beside the existing trash icon on each rail row, so a workflow can be
shared without opening it first. Trash keeps its current always-visible styling.
The button is faded out rather than unmounted when the row loses hover:
ShareButton owns the modal's open state, so unmounting on hover-out would slam
the dialog shut the instant the pointer left the row to reach it. Keeping it
mounted also stops the row reflowing as the icon appears.
Adds the Share affordance to the Workflows hub, left of the close button. It
only appears in detail view, so it always has an unambiguous target; goHome
leaves selectedId set, hence the explicit mode check.
Everything under it already existed: WorkflowExportable is registered and
ShareKind already had 'workflow'. The hub simply never rendered a ShareButton.
The title bar moves from the card into the content. The card is at 281 of the
linter's 300-line cap, so lifting nav state up into it would have breached the
cap; pushing the bar down to the state instead keeps both files well under, and
splits on a better seam anyway (card = drag/resize geometry, content = the app
and its chrome). The card hands its drag handlers down via CardHeader.
The share dialog portals to the body but its events still bubble the React tree,
so the wrapper stops click/pointerdown; without it, dragging the card follows a
click inside the modal.
Verified in the running app: share icon absent on Home, present in detail and
left of the X, and the modal builds a real workflow bundle.
The module was written when the workflow store lived only on eric/workflow, so
every store touch went through p_store()/p_model() try/except-returning-None.
Both resolve on this branch (the round-trip test already asserts as much), so
those guards had become dead code that would swallow a genuine ImportError in
storage.py and surface it to the user as "this build doesn't support workflows
yet; please update OpenSwarm".
Imports storage/Workflow at module level, drops the None branches, and rewrites
the stale docstring. The safety contract is unchanged and still covered by
test_workflow_sanitize_disables_schedule_and_strips_pii: an imported workflow
never auto-runs, and the sharer's phone number never rides along.
The Downloads scan rejected any file whose mtime was newer than the scan's
Date.now() snapshot (sub-ms fs timestamps, NTP stepping the clock back), and
the ambiguity test was green only because both fixtures were skipped by that
guard. Allow 60s of future skew, add a win32 end-to-end bind test, and cover
every stamped artifact name (mac arm64/x64 dmg, windows setup exe, linux
AppImages) plus unstamped negatives in the parser test.
Electron resolves ONE install id before spawning the backend (install.json
app_install_id -> python settings installation_id -> fresh uuid) and exports
it as OPENSWARM_INSTALLATION_ID; the backend adopts it when settings has no
installation_id yet. The affiliate app_install_id and the analytics
install_id are now the same value, so affiliate refs join directly to
telemetry with no sign-in required. Drops the frontend app_install_id
forwarding through sign-in flows; the desktop auth router forwards
settings.installation_id as install_id instead.
The optimistic spawn placed the chat with placeCard (which grid-snaps + collision-dodges) then immediately overwrote it with setCardPosition to force the exact viewport-center/beside-card spot. Fold that into a single placeCard({ ..., exact }) so the pre-resolved position is honored directly; the single-browser dock omits exact to keep the collision-dodge cascade.