Skip to content

Releases: aimhuge/weft

Weft 0.6.6

Choose a tag to compare

@fotoflo fotoflo released this 29 Oct 03:12

Added

  • Issue cards now stand out, and a project can work them itself. Each project picks where its imported GitHub issue cards land, in the GitHub drawer under the sync switch: Backlog (what it did before — a cold card, yours to pick up), Needs you (the card is made there, so a fresh import is visible at a glance and still follows its issue until you run it: a new label reaches it, a closed issue archives it), or Run (the card's thread starts at once and the agent works the issue, parking in Needs you when it needs a decision and going to Review when it finishes, exactly as any other thread does). A Run card can be checked before any work happens: Checks with names the skill that runs as the card's first turn, and Works as names the agent that works it. Design the agent starts a new thread that designs that agent for you: Weft does not ask the questions, the thread does — it asks what it needs to know, one question at a time, then writes the soul and saves it as a real agent whose soul opens every system prompt its threads get. The thread opens on its own page: the brief it is running on and what happens next, written before the turn starts so the pane has something current the moment it opens. The workflow is per project and saved with your settings; changing it changes where the next cards land, and the cards already on the board stay where they are. The landing and its skill and agent live in lib/issue-landing.ts and lib/issue-run.ts, the designing thread in lib/agent-thread.ts with its brief page in lib/agent-brief-page.ts, the drawer in components/IssueWorkflowPicker.tsx, and the route in PATCH /api/github/issues and POST /api/agents/design.
  • The Weft Runner is designed: a headless agent runner on a host that does the compute for a card, while the board stays on your laptop or on the web at <workspace>/board. Reading the code first turned up the fact the design rests on: Weft already has a remote control plane (the web board commands a Mac across the network through cloud_commands) but no remote compute plane (the CLI always spawns on the board's own machine). A runner is therefore not a new transport but a second implementation of the executor behind that same command bus — the run kind carries a Spec (lib/runner-state.ts), and the turn comes back through the same handle(id, session, msg: SDKMessage) seam the Codex and Goose runners already use (lib/runner-turn.ts:36), so the thread rendering, the reply box, the permission prompts and the web transport are all unchanged. A cloud_hosts registry with heartbeats turns the web's "Waiting for your Mac" into a real liveness signal, and command leases replace the rows a crashed runner would leave claimed forever. The design also names the twelve places a host is not "the Mac, but headless" — the guards and cross-card claims are in-process today and become distributed, paths and repos need host-relative identities, the pending permissions have to become state rather than a promise, the worktrees need a Linux implementation, and the 🔐 vault needs a host-scoped key. The phases: the daemon alone (a Mac card running on a server) → addressing and liveness → shared claims → host-side MCP and secrets → the host's own board, where this converges with headless server mode. The design is in docs/architecture/runner-host.md; the ROADMAP's "Remote runners" moves from P3 to P2.

Changed

  • The Esc view hint is now a loud toast — it was a grey box in the bar's own grey, 11.5px, gone in 2.6 seconds, and it read as a tooltip rather than an announcement. It is now big type, an accent tile carrying the view's icon, a bright accent border and a wide glow, with a short pop-in so it reads as an announcement. It stays up for 4 seconds, and says what the next Esc does ("List view · hit Esc again to switch to board view") instead of a vague "Esc goes back". It is one row and never wraps. An absolutely-positioned box with no width shrank to its content and then wrapped its text into a narrow column, which left the icon adrift from the words and stranded the ✕ between them, so it now carries w-max and whitespace-nowrap. The next Esc puts the toast away instead of flipping the view again — the toast advertises that Esc does something, so Esc has to do it. The flip itself still works the same: the toast is driven by the board's own Escape layer, so it appears on exactly the Escape that moves the view. Every flip pops the toast afresh. The flip's id counted the flips but nothing read it, so a second Esc left the toast sitting there already grown, with its entrance long over. It is keyed on the id now, so each flip remounts it and the pop-in plays again. And the toast's ✕ was wired through an optional prop that defaulted to a no-op swallowing the click, though the board has always passed it; it is required now. The toast is components/ViewHint.tsx with its state and timer in lib/use-view-hint.ts, the flip's hook lib/use-escape-view.ts (whose extra inputs are one options object, not a list of six), the pop-in and glow in app/globals.css, and the bar that hangs it under the toggle in components/ProjectBar.tsx.

Fixed

  • The dictation key answers to Command+Option+' and stops the mic. The rebindable binding is a set of modifiers rather than an exact combination, so the default ⌥' now fires on ⌘⌥' too — the combination people reach for when the bare one is taken. Pressing the key again while the microphone is still opening cancels the recording that is starting, instead of leaving it running with nobody listening or opening a second microphone on top of it (lib/shortcuts.ts, lib/use-dictation.ts, lib/use-composer-keys.ts).
  • The packaged app starts again — it had been dying on launch with "Cannot find module '@supabase/supabase-js'". The shared vault added a Supabase client to Electron main, but electron-builder's files list drops the whole top-level node_modules (!node_modules${/*}), and the package was never named back on the list. It was in package.json's dependencies and in the Next server's standalone bundle, so the dev build and every dependency check were happy — but the shipped app's node_modules is only the allow-list, and the client wasn't on it. Every packaged build since died in the main process before a window opened, and no test could see it, because the tests run against the repo's node_modules, where the package is present. The list is now derived rather than hand-kept. scripts/electron-runtime-deps.mjs computes the closure of packages Electron main needs at runtime — the bare require()s out of electron/*.cjs, then the runtime requires out of each package it finds, transitively — and pnpm dist writes it into build.files before packaging. A new require() ships with its package instead of waiting for someone to remember. Both the CommonJS and the ESM entry are read, because a package's main can vendor a dependency its module build imports (realtime-js's dist/main vendors its own phoenix, its dist/module imports @supabase/phoenix). The built app is checked before it ships. scripts/verify-packaged-app.cjs reads the archive itself — a package counts as shipped only if app.asar holds it, never by resolving from the repo's node_modules, which has everything and would hide a broken build — and then loads the packaged account-client.cjs out of the archive and builds a Supabase client from it. electron/packaged-deps.test.ts fails if build.files drifts from the closure. The packaged app's bundled pnpm (which already ships in Contents/Resources/toolchain) is checked too: scripts/verify-packaged-toolchain.cjs confirms it runs, matches packageManager, and can install a project's dependencies. The packaging section of docs/architecture/desktop-shell.md says what the allow-list is and how it is derived. The app now checks its own bundle before anything needs it. The derivation and the build check catch a missing package before it ships, but a bundle can still arrive broken another way — an interrupted install, a half-copied update — so at boot the app reads its own app.asar and says what is missing: "Weft couldn't find some of its own files (missing from the app bundle: @supabase/supabase-js). Reinstall Weft to repair it." instead of dying on a require with a native "A JavaScript error occurred in the main process" dialog nobody could act on. The check reads the bundle, not the machine: inside app.asar Node's resolution walks out of the archive and finds the real node_modules next to the app, so a plain require.resolve() would report "found" for a package the bundle doesn't have — the same trap verify-packaged-app.cjs documents, and the first version of the check fell into it and called a broken bundle good. The Supabase client is now required lazily, so a bundle missing it still opens a window and can say so. An auto-update runs pnpm install — for the one tree that needs it. Squirrel.Mac replaces the whole .app bundle, so an update brings the app's own dependencies with it: app.asar's packages and the Next standalone bundle are already installed when the app restarts, and re-installing them would write a node_modules folder nothing reads. The one tree installed separately from the bundle is the toolchain's puku tree, so that is what pnpm install runs against — after an update, and only then, in the background, because the board never waits for it. The app knows an update landed by the version changing since the last launch (~/.weft/updated-version), so an ordinary relaunch installs nothing and only checks the tree. The check and the install live in electron/runtime-deps.cjs, electron/toolchain-install.cjs, electron/updated-since.cjs and electron/boot-deps.cjs, with tests beside them; docs/architecture/updates.md says what an update brings and...
Read more

Weft 0.6.5

Choose a tag to compare

@fotoflo fotoflo released this 28 Oct 07:24

Added

  • Your 🔐 secrets now follow you across machines — a vault can be shared with your workspace, and every Mac you own (and every teammate you invite) reads and writes the same secrets. The server never holds a value: your device has a keypair, the vault's key is sealed to each device's public key, and each secret is encrypted under that key before it leaves the Mac. The server stores ciphertext and nothing else, so it can only ever be trusted with what it cannot read. Sharing it — the drawer's Share this vault with <workspace> turns a local vault into a shared one; the header then shows the workspace's name, a shared badge and the key version. A teammate's machine is added by fingerprint: both sides show the same string, you compare them out of band, and only then is the key sealed to that device. Devices is a new tab on the workspace's Team page. The desktop's keypair never leaves the machine it was made on, and Weft's servers can't substitute their own — a fingerprint that doesn't match stops the grant before any key is produced. Working in it — the drawer pulls changes when it opens and follows them live, a teammate's edit shows in the event log beside the secrets (who did what, never a value), and if your change loses a race the row says so: "Sam changed API_KEY in development — keep your edit?", with both ways out. Reads work offline from the local shadow; writes made offline queue up and drain in order, and nothing is dropped while a conflict is unresolved. When people leave — removing a teammate can rotate the shared vaults: the key version is bumped, a fresh key is made on your own Mac and sealed to the machines that stay, and every value is re-encrypted under it, so the plaintext they held stops being worth anything. Weft then names the values to change at their providers — the names only. A vault's recovery key is 24 words that open it when every machine is gone: Touch ID asks, the words appear once to write down, Weft keeps no copy, and asking again makes a new one so the old words stop working. The schema is supabase/migrations/20261027163113_team_vault.sql; the crypto is electron/vault-crypto.cjs, the sync engine electron/vault-sync.cjs, the grant flow electron/vault-grant.cjs, the rotation electron/vault-rotate.cjs and the recovery key electron/vault-recovery.cjs, with the workspace side in app/[orgSlug]/team/devices/ and app/api/team/vaults/.
  • Esc now says where it took you — pressing Esc on a bare board flips between the columns and the list sheet, and a small pane now pops under the board/list toggle naming the view you landed in ("List view. Esc goes back."). It hangs off the toggle rather than taking a row, so it costs no vertical space on a small screen, and it closes itself after a moment or on its ✕. The hint is driven by the board's own Escape layer, so it appears on exactly the Escape that moves the view: with a thread, the new-thread dialog, the browser pane or a closable drawer open, or with Esc typed in a field, the view stays put and no pane appears. Escape from a page in the browser pane now reaches the board too — a focused page swallowed the key, and it arrived as a raw key-down that the pane's replay dropped, so the flip never happened from inside a page. The pane is components/ViewHint.tsx with its state and timer in lib/use-view-hint.ts, the flip's hook lib/use-escape-view.ts, and the pane's Escape rule electron/pane-escape.cjs.

Changed

  • The model picker closes when you pick, and every row is a model — no more "Let Claude Code choose" or "Let the model choose" in a model menu: each row is the model that will actually run, and a menu with nothing picked shows the model its CLI runs. Codex and Goose, whose model lives in their own config, each keep one row for it (Codex's configured model, Your BYOK model). The list is taller, so more models fit before it scrolls, and it closes on a pick — the field sits inside a label, and the click that used to refocus the input and reopen the list is cancelled now. The Goose group lists your provider's models, not a fixed set — a menu's Goose rows are the provider you set up (Settings → Models → Bring your own key): OpenRouter's anthropic/claude-opus-4.1 never shows under Anthropic any more, and a local Ollama shows the id it runs. A Goose id is recognised through the provider catalog, so a model still routes to Goose, and a Goose row's value is prefixed so it can't be mistaken for another CLI's same id. Name your models — Settings → Models → Bring your own key has a Model names section: a name beside a model id ("Big Opus" for anthropic/claude-opus-4.1), and every model menu in Weft shows that name with the id beside it. The id field offers your provider's own model ids as suggestions. The picker is components/ModelTypeahead.tsx and components/ModelSelect.tsx, the rows lib/model-options.ts and lib/new-thread.ts, the provider's models lib/goose-provider.ts, and the naming UI components/GooseNamedModels.tsx with lib/use-named-models.ts and the settings route.

Fixed

  • A Goose model is no longer dropped when a thread starts. Picking a model from a Bring-your-own-key provider and starting a thread with it ran on the provider's default instead of the model you picked, with nothing to say so: the id was checked against a pattern that allowed neither the "/" in an OpenRouter id (anthropic/claude-opus-4.1) nor the ":" in a local tag, so it was read as no model at all. The check now allows the characters a real model id uses, and the "goose:" prefix a picker adds comes off at the card's edge, because a card's model is the id the CLI takes. (lib/new-card.ts)

Weft 0.6.4

Choose a tag to compare

@fotoflo fotoflo released this 27 Oct 16:26

Changed

  • The copilot is now your product partner, not a second inbox. It helps you decide what to build next, shape an idea into cards and keep the code healthy. It no longer recites the board. The launcher's number (90 and climbing) is now a dot that only lights when the copilot has said something you haven't read, and the peek shows its latest idea instead of a list of cards. It also speaks first, at most three times a day and never from 21:00 to 7:00: a morning idea drawn from your roadmap, recent commits and analytics, and a refactor worth doing after work lands in a project. When it has nothing worth your time, it answers QUIET and the check-in leaves the chat. Turn this off with "Checks in on its own" in the copilot's name menu. (lib/copilot-initiative.ts, lib/copilot-initiative-run.ts, lib/board-tools-trust.ts, components/CopilotHider.tsx, components/CopilotPeek.tsx, lib/copilot-sources-product.ts)

Fixed

  • The Skills menu no longer gets cut off in a narrow thread panel. It now opens over the whole window: under the Skills button, slid back inside the window and clear of the browser pane, and only as tall as the room below. Before, it was drawn inside the thread panel, so its left side was cut off. (components/SkillMenu.tsx, lib/float-place.ts)
  • Puku threads no longer say "Not logged in" in projects that use Puku's API themselves. puku-cli loads the .env files of the folder it starts in, so a project's own PUKU_AI_API_KEY (BlastCP's, say) replaced your Puku login. Logging in again didn't help, because Weft checks your login from its own folder. Now a puku thread ignores the project's Puku keys when it signs in, and its commands still see them, so the project's dev server and tests keep working. (lib/puku-dotenv.ts, plugin/env/puku-bash-env.sh, lib/puku-idle.ts)
  • "Allow once and retry" now works on every call auto mode blocks. Auto mode treats some calls as classifier-only: Weft's allow didn't count for them, so when the classifier gave no verdict ("it skipped this action"), the retry was blocked again with the same error. Now the thread leaves auto mode just for the retry, Weft lets the call you allowed through, and the thread goes back to auto mode right after. The card's own mode never changes. (lib/classifier-denial.ts, docs/architecture/thread-guards.md)

Weft 0.6.3

Choose a tag to compare

@fotoflo fotoflo released this 26 Oct 21:14

Added

  • A progress bar when you clone a repo. Cloning from the repo picker or the project menu's Clone from GitHub now shows how far git has got (receiving objects with size and speed, resolving deltas, checking out) instead of a bare "Cloning…" (lib/clone-progress.ts, components/CloneProgress.tsx, components/ProjectCloneForm.tsx).
  • Fold any column, and let the full ones spread out. A chevron in every column's header folds it to a thin strip with its name and count, even while cards wait in it. Click the strip to open it again; cards dragged onto it still land there, and Weft remembers which columns are folded. When the board has room, the column with the most cards spreads them over two columns, then the next fullest, as long as its cards don't fit its height and another column's width still fits. Narrow the window and they go back to one (components/FoldedColumn.tsx, components/BoardColumns.tsx, components/BoardColumn.tsx, lib/board-overflow.ts, lib/board-prefs.ts).
  • After an update, Weft says what's new in a few lines. The first time Weft opens on a new version, a small What's new in Weft X splash lists up to three changes you'll notice, each with its payoff, and sometimes a short video, with a link to the full release notes. Weft's Copywriter writes the lines when the release is cut, so they read like Weft and not like a commit log. New installs meet the welcome instead, and Settings → General → Reset tips shows the current splash again. Check for Updates… is now in the File menu too. (components/ReleaseSplash.tsx, lib/release-splash.ts, lib/release-splashes.json, lib/use-release-splash.ts, scripts/release-splash.mjs, electron/menus.cjs)
  • The weekly repo-analysis report now reads the real systems, instead of the numbers it was written with. The report is a file in ~/.weft/show, so every figure in it was a snapshot from the day it was written. Now a guarded route (app/api/infra/live) reads the project's actual numbers and the page asks for them on open: the database size, the file storage and its object count, the auth users, the provider states, and the quota rows with how much of each ceiling is left. The egress is measured too — it has no API of its own, so Weft reads the Analytics log stream and splits the response's content_length by cache status, which is the difference between the cheap CDN rate and the full one. A missing credential never fails the read: that half comes back unread and the report says so plainly, rather than showing a stale number as if it were live. A file: page reaches the route through Weft's own bridge (electron/live-data.cjs): it names a path, never a URL, and Electron main holds the launch token so the page never has it. pnpm infra:probe prints the same snapshot the report reads.
  • Your Weft plan now comes with BlastCP, and BlastCP applies it. If you're connected to BlastCP with your Weft account, your own BlastCP workspace moves to BlastCP Creator (every channel, unlimited posts, $3 a month of AI generation) while you're on a Team seat or own a Pro, Open Source or Education workspace. It goes back to BlastCP Free when that ends, and a BlastCP plan you pay for yourself is never touched. Weft tells BlastCP when you sign in, twice a day, and as soon as your plan changes, so a Checkout shows up in BlastCP within seconds. The pricing cards, upgrade dialog and access row list the BlastCP tier again and say who gets it ("for the owner", "for every seat"). Main files: lib/billing/blastcp-bundle.ts, app/api/billing/blastcp/route.ts, lib/blastcp-plan.ts, components/home/Plans.tsx.
  • Z reopens the last card you had open. Close a card, press Z on the board, and it opens again: the same card that stays outlined after you close it. Undo the last archive moves to Shift+Z (the toast's Undo still works). Files: components/board-shortcuts.ts, components/use-board-commands.ts, components/board-palette.ts.

Changed

  • The privacy policy now covers Google account data. A new "Google user data" section on weft.zip/privacy says what Weft's Google sign-in reaches (Gmail, Calendar, Drive, Docs, Sheets, Slides, Contacts, Tasks), that the token stays in your Mac's Keychain and the data goes from Google straight to your Mac, never through weft.zip, that it's used only to do what you ask, and never sold, used for ads or to train models. It states Google's Limited Use terms, which Google's OAuth review looks for before Weft's own Google client can leave Testing. (app/privacy/page.tsx)

Fixed

  • Project Sync follows you to your other Macs. Sign in on a second Mac and every project you sync elsewhere turns on there too, matched by its GitHub repo rather than its folder path, including a project you've just cloned on that Mac. Before, a new Mac started with every project off. The switch is kept per repo in your account (lib/cloud-sync-repos.ts, cloud_sync_repos).
  • A "use client" module that reaches a Node built-in no longer takes the board down. The board 500'd with nothing the UI could show — Turbopack panics on node:fs ("the chunking context does not support external modules") and the whole route goes with it. The model menu was the culprit: it read the models you named straight from lib/goose-models.ts, which reads the settings file off disk. It reads them from the server instead now (lib/use-named-models.ts), the way the plan limits already were. And the class of bug is now a lint rule: a "use client" file that reaches a Node built-in, "server-only" or "use cache" fails pnpm lint (weft/no-client-server-leak), so the pre-commit hook refuses the change instead of the next restart. It reuses the analysis lib/client-graph.test.ts already walked the real tree with, and names the chain it found. Files: eslint-rules/no-client-server-leak.mjs, eslint.config.mjs, lib/use-named-models.ts, lib/settings.ts, lib/types-settings.ts, components/ModelSelect.tsx.
  • weft.zip offers a new release within minutes. The home page's download button and weft.zip/download kept pointing at the old dmg for up to an hour after a release, because the release lookup was cached for hours from the last deploy. It's now re-read at most once a minute, so a new version shows up on the site within a couple of minutes, without a redeploy. (lib/home-release.ts)
  • Slim scrollbars, and cards that stay inside their column. Every scroll area now has a slim rounded thumb in the theme's line colour on a clear track, brighter on hover, instead of the heavy grey default bar. A board column no longer scrolls sideways: a card with a long footer (several tags, "+1 here", age) used to push past the column's edge and add a horizontal bar. The tags now shrink with an ellipsis and the footer clips to the card. Files: app/globals.css, components/BoardColumn.tsx, components/CardMarks.tsx, components/CardFooterMeta.tsx.
  • The model picker shows "Let Codex choose" once. In a menu that also picks the CLI (New thread, Settings → Models' default), Codex and Goose each showed two "Let … choose" rows. The second one picked the sentinel model id instead of moving the thread to that CLI, and Codex's row was noted "Runs Let Codex choose". Now each CLI gets one row that picks it. The typeahead's CLI groups are also named for screen readers again, as the old menu's groups were. Files: lib/model-options.ts, components/ModelTypeahead.tsx. The picker's tests now drive the typeahead itself through components/model-picker-test-utils.ts.

Weft 0.6.2

Choose a tag to compare

@fotoflo fotoflo released this 26 Oct 18:25

Added

  • Weft asks to turn on Project Sync for the project you're working on. Once a thread in a project reaches Review or Done, a "Turn on Project Sync for ?" dialog explains that synced projects keep their cards on your web dashboard while they run on your computer, so you can manage your Weft from the web, and that teams can watch each other's work and contribute in real time. It offers Sync, Not now and a Don't show me again box. It asks at most once a launch and never about a project that already syncs. Settings → General → Reset tips brings it back. (components/SyncPromptDialog.tsx, lib/sync-prompt.ts, lib/use-sync-prompt.ts)
  • A feedback dot in the board's corner. A small dot at the right end of the footer opens a chat where an AI asks what's in your way, then a few short follow-ups. Once it has enough (or you click "That's all"), it writes a summary for you to check. Send puts it in the Weft team's Slack, signed with your Weft account when you're signed in, and keeps a copy in ~/.weft/feedback/. Built in components/FeedbackDot.tsx, lib/feedback.ts, lib/feedback-send.ts and app/api/feedback.
  • 🏅 73 new badges, Weft explorer and secret ones. The Wefter Profile now has 143 badges in 17 families: Speed, Calendar, bigger Milestones and Streaks, Tokens & spend, Craft, Board, Projects, Leagues, Social, Undeterred among the Limits, the ❓ Secret ones (hidden until you earn them) and Weft explorer, which logs your first use of the vault, the webhook relay, a dev server, a pane screenshot, a card or app share, your profile and every feature intro. Old badge ids are unchanged, so nothing announces again. League and social badges read the new badge_facts() RPC on the league timer (lib/badge-kit.ts, lib/extra-badges.ts, lib/*-badges.ts, lib/explore.ts, lib/badge-facts.ts).
  • Super admins can now live in the database, not just in SUPER_ADMIN_EMAILS. A new public.super_admins table (user_id, email, created_at) lists accounts that see the Admin pages and rail icon, so adding one needs a row, not a redeploy. A signed-in account can read only its own row and nobody can write to the table through the API, so no one can add themselves. The env list still works as the bootstrap, and the gate checks it first. Any lookup error reads as "not an admin". Files: lib/admin.ts, supabase/migrations/20261025171511_super_admins.sql, docs/architecture/admin.md.
  • The card that asked for a restart now stands out. When a thread called mcp__board__restart_weft_when_idle, Weft showed one red pill in the header — but the card that asked looked exactly like every other card, and it wasn't recorded anywhere at all. The asking card is now remembered in the restart drain (RestartStatus.requestedBy) and marked on the board: a thin red ring with a soft glow, and a ⟳ Restart asked pill at the start of its title, drawn like the header's pill so the card and the pill read as one thing. The card keeps its own layout: no side bar, no wash, nothing squeezed beside its picture. Files: lib/restart-tool.ts (the tool names the card that asked), lib/restart-when-idle.ts (the drain keeps it), lib/types-card.ts (RestartStatus.requestedBy), components/RestartMark.tsx + components/restart.css (the mark), components/CardView.tsx and components/Board.tsx (the card draws it).
  • Weft's server can read Slack now, not just post. The Slack rail could only send: the bot token stayed sealed in Electron main, so the server had exactly one way in — post this message. Anything that needed to read from Slack had no door at all. There's a new control route, POST /slack/call, that makes any Slack Web API method with the token main already holds (conversations.history, auth.test, anything), and POST /api/slack/call in front of it so the server can use it. The token still never leaves Electron main, and the server still never sees it. The app manifest also declares the incoming webhook the deployed Weft posts feedback through, and the docs now spell out that Slack has no Web API method for incoming webhooks — the feature is turned on by the manifest or by hand, and the URL is shown once. Files: electron/slack.cjs, app/api/slack/call/route.ts, lib/slack-manifest.json.
  • The board as a sheet. A toggle in the project bar swaps the columns for a list: every card on one table, a line each, with the header stuck to the top and the columns lined up down the sheet. The columns are title, status, stage, tags, folder and age; the status reads Needs you for a card waiting on you, and the folder carries a +N when other cards share its working tree. The colour lives in the group heading rather than a bar on every row. Which view you last used is remembered. On a narrow board the sheet drops the roomiest columns — the tags and the folder, then the age — instead of squeezing the title away, because it answers to the width of the board itself rather than the window. Files: components/BoardList.tsx, components/CardRow.tsx, components/list-grid.css, components/ViewToggle.tsx, lib/board-prefs.ts, lib/use-view-mode.ts, lib/board-view.ts.
  • Agents: pick who a thread is, starting with Weft's copywriter. The New thread dialog has an Agent menu beside the model menu. 🛠 Builder is Weft's own soul, the one every thread had until now: it plans, builds, tests and ships. ✍️ Copywriter writes in Weft's voice (the home page, feature intros and voiceovers, changelog entries, posts, UI copy) and keeps every tool a thread has, so it can read the product, show drafts in the browser pane and put the copy where it lives. Right-click a card → Agent: … to switch it between turns: the conversation carries on under the new soul. A ✍️ in the card's footer marks a copywriter, a cron keeps its source card's agent, and threads and the copilot can start one with create_card's agent. The copywriter's soul is Weft's style guide: Weft as the subject, "your AI", the payoff in a "so" clause, no plumbing and no hype. lib/agents.ts, lib/weft-prompts.ts, components/NewCardDialogControls.tsx, lib/card-menu.ts, docs/architecture/agents.md.
  • Feedback can reach Slack without anyone ever pasting a webhook URL. Slack has no Web API method for incoming webhooks — you cannot create, list or fetch one — so the only way to get the feedback channel's address is for a person to install the app and pick a channel. Weft now does that itself: /api/slack/oauth/install sends you to Slack's channel picker, Slack redirects to /api/slack/oauth/callback, and the callback exchanges the code with the app's Client ID and Secret and writes the webhook straight into public.slack_webhooks. The collector reads it from there, so the address is a credential that never passes through a chat, a log line or a page — the only things you supply are the app's ordinary OAuth credentials. Files: lib/slack-oauth.ts, lib/slack-webhook-store.ts, app/api/slack/oauth/install, app/api/slack/oauth/callback, supabase/migrations/20261026040000_slack_webhook.sql, lib/feedback-send.ts.
  • Crons in the list view. The sheet ends in a ⏰ Crons band with a row per cron. Each row shows whether the cron is on and when it runs next, its schedule in words, whether each run forks a thread or starts fresh, its folder, when it last ran and how many runs it has made. Click its status to run it now or switch it on or off, and unfold it to see its run cards, each one openable. Elsewhere in the sheet, a run carries a violet "⏰ run N", and the thread its runs fold under says "⏰ next in 3h" (or "off"). "⏰ cron" in the Tags filter cuts the sheet down to the crons and the threads they come from. Files: components/SheetCrons.tsx, lib/list-crons.ts.
  • Type to search, Space B to add, drag to reorder in the list view. In the list, just start typing and the sheet searches titles from your first letter. Space then B jumps to the Backlog's add row (unfolding it) so you can type a title right away, and Space then R starts a thread. Board shortcuts that are single letters move behind Space in the list: Space N for New thread, Space T for the terminal. J and K still move through the cards. Backlog rows drag into order by their row number, as on the board. Dragging is off while the sheet is sorted. Files: lib/list-keys.ts, components/use-list-keys.ts, components/use-sheet-drag.ts, components/BoardList.tsx.
  • Each kind of update can have a channel of its own. The Slack drawer had one channel per project, so every commit, deploy and card status from a project landed together and there was nowhere else to put them. Under Channel per kind each kind — commits, pushes, deploys, card status, growth — can now have a channel of its own, and a kind with one goes there whatever project it happened in: the kind's channel is the most specific answer, so it beats a project's channel and the default. No channel puts a kind back to following the project. Files: components/SlackRoutes.tsx, components/SlackSettings.tsx, lib/slack-route.ts, lib/slack-store.ts, lib/slack-types.ts, lib/use-slack.ts.
  • Growth lands in Slack as it happens, and a summary goes out every morning. A new user, a new team and every payment now post to the growth channel. The numbers come from production Supabase — a new user is a row in auth.users, a new team is an org with more than one member (every sign-up makes a personal org, so counting orgs would count sign-ups), and a payment is a billing event with subscription.synced filtered out so one subscription doesn't count five times. Weft remembers where it got to in a watermark beside the rest of the Slack settings, and it only moves after a successful post, so nothing is posted twice and nothing Slack refused is lost. The first look goes back a week, so a channel switched on today still says something about the recent past. Once a morn...
Read more

Weft 0.6.1

Choose a tag to compare

@fotoflo fotoflo released this 25 Oct 10:57

Added

  • Every project has a changelog Weft keeps, so cards stop colliding on CHANGELOG.md. Threads record what they ship with mcp__board__changelog_add (_list, _edit) into ~/.weft/changelog.jsonl, mirrored to Supabase changelog_entries while you're signed in. Each entry links to the card that wrote it, and the card's own commit stamps it with the sha (draft → committed). Right after that commit Weft rewrites CHANGELOG.md and commits it alone as docs(changelog): <card>, keeping lines typed by hand; a commit that bumps package.json's version gets that version's section, so nobody runs a release step (pnpm changelog:release <version> stays for a bump Weft didn't see). More → Changelog of or ⌘K opens a read-only list, Unreleased then each version. A Markdown-only commit now skips the pre-commit typecheck. (lib/changelog*.ts, components/ChangelogDialog.tsx, scripts/check-staged.sh, docs/architecture/changelog.md)
  • A shared mini app's link preview refreshes itself, and the card is drawn once. The preview used to be made once, when the page changed, and then it sat there: a data app's numbers moved and its picture didn't, and a capture that failed never came back. Each preview now carries when it was made (at), and isStale (lib/app-preview-capture.ts) says when it needs doing again — the page changed, it's older than PREVIEW_MAX_AGE_MS (6 h), or it should have a picture and hasn't got one and PREVIEW_RETRY_MS (30 min) has passed. A timer walks the shared apps and refreshes the stale ones, one at a time (lib/app-preview-refresh.ts), so the hidden capture window and Haiku never pile up; it starts and stops with cloud sync. A refresh of an unchanged page reuses what the last one had: Haiku's sentence stays, and a capture that fails keeps the last picture but leaves shotFailedAt, so the retry is never lost. The Share dialog's card is cached beside the capture (~/.weft/app-previews/<app id>.card-<hash>.png), so opening it again doesn't redraw it, and a save that only changes who may open the link no longer re-uploads the page's bytes (pageForWrite in lib/app-share-row.ts). A tick that changes nothing reads nothing: each file's sha is cached by its path, size and mtime (lib/app-file-sha.ts). Files: lib/app-preview.ts, lib/app-preview-capture.ts, lib/app-preview-refresh.ts, lib/app-share-preview-actions.ts, lib/app-share-row.ts, lib/app-file-sha.ts, lib/cloud-sync-start.ts, docs/architecture/mini-apps.md.
  • A new thread waits for the limit instead of dying on its first turn. A thread created on a model that has hit its limit used to fail immediately and sit dead in Needs you. Weft now knows before the turn starts: the New thread dialog's Start button reads Start at 2:30am, says which limit is used up, and the card waits for the window before its first message goes out — the scheduler sends it the moment the window opens, and a card that never ran is fine to schedule. One answer covers every limit a thread can meet — the model's own cap (Opus can run out before the CLI's window does), the CLI's weekly window and its 5-hour window — in one pure module read as data, so the server (lib/start-gate.ts, lib/new-card.ts) and the browser (/api/limits/plans, lib/use-start-gate.ts) always agree; the client only labels the button, the server decides. The reply box reads the same gate, so a model's own cap and the weekly limit now reach it too, not just the 5-hour window. Files: lib/limit-reset.ts, lib/start-gate.ts, lib/use-start-gate.ts, lib/new-card.ts, lib/reply-mode.ts, app/api/cards/route.ts, app/api/limits/plans/route.ts, components/NewCardDialog.tsx, components/NewCardDialogFooter.tsx, components/ReplyBox.tsx, components/ThreadFooter.tsx, components/ModelSelect.tsx.
  • Deleting a card asks in Weft's own dialog, with a "Don't ask again" tick. The trash button in an open card's header and the right-click menu's Delete used to open a native window.confirm, which is a macOS sheet with only Cancel and OK — no room for a checkbox. Both now open components/DeleteCardDialog.tsx, Weft's own modal: it names the card, says its CLI transcript stays on disk, says the delete stops a running turn, and carries a Don't ask again tick. Ticking it writes the flag in lib/delete-confirm.ts (localStorage, like the Switch all dialog's) and later deletes go straight through; Settings → Security's Ask before deleting a card row is the way back. The dialog's state and the board's delete live in lib/use-delete-dialog.ts. Files: components/DeleteCardDialog.tsx, components/DeleteConfirmSwitch.tsx, lib/delete-confirm.ts, lib/use-delete-dialog.ts, components/CardActions.tsx, components/use-board-menus.ts, components/SettingsSecurityPage.tsx, components/BoardDialogs.tsx, components/Board.tsx.
  • Open-source maintainers get Weft's Open Source plan free for a year, checked on GitHub when they sign in. Two $0 plans join the catalog, Open Source (oss) and Education (edu). Both unlock teams and BlastCP Creator and stay on the free tier's Puku cap (FREE_TIER, TEAM_PLANS, GRANT_PLANS). A GitHub sign-in on weft.zip checks in the background, after the redirect: if you maintain a public, non-fork, non-archived repo with an OSI licence, pushed in the last 90 days, with 25 stars or 20 commits in those days, the workspace moves onto Open Source for 12 months. A sign-in in its last month renews it, and the billing page has Check my GitHub now (lib/billing/oss.ts, lib/billing/oss-grant.ts, lib/billing/grants.ts, app/auth/callback/route.ts). The grant's end is kept in the subscription's trial_end, because the billing package rolls a $0 row's period forward on every read. A lapsed grant reads as Free in the team gate. Regional pricing is built on Weft's side: World Bank low-income and lower-middle-income countries, by IP or billing address, show Pro at $5 or $10 a month on the billing page and pass a price variant to Checkout (lib/billing/regional.ts, lib/billing/regional-pick.ts). Selling it still needs @aimhuge/billing price variants. Education's plan and grant are built, and its redemption waits on the GitHub Student Developer Pack. Deleting a workspace on a granted plan is no longer blocked as "still billing" (lib/team-delete.ts).
  • Level-ups open like an episode, and you can replay the last one. A level-up popup now opens on a picture of your rank: six BlastCP renders of our hovercar, every one wearing the Weft emblem, from a dusty garage at Thread through the canyon run (Yarn), the neon city (Bolt) and the snowy-pass formation (Loom) to a cliff over the woven city (Tapestry) (public/brand/levels/). Over the picture rise the level as an "Episode" number in roman numerals ("Episode XXIV") and a line of story, tilted like an opening crawl; each rank has a few lines, and each level takes the next one (lib/level-lore.ts, components/AchievementPopup.tsx, .level-hero-art / .level-crawl in app/globals.css, both still with Reduce motion on). Replay last achievement, in the avatar's menu and the command palette, opens the last popup again, or a level-up to your current level if none has opened yet (lib/achievement-replay.ts, components/GameBadge.tsx). The avatar menu moved into its own components/AccountMenu.tsx.
  • The card that crashed or restarted Weft is marked on the board. When a thread stops Weft's server or leaves a file that doesn't build, its card gets a red ring and a 💥 Broke Weft chip (×N if it does it again). Hover the chip to see what it did; × dismisses it. The thread is told why ahead of its next message, once, so it doesn't do it again. Electron notes a server exit Weft didn't ask for (electron/server-exit.cjs), the board reports build errors and full reloads (lib/use-break-report.ts → POST /api/broke), and lib/crash-blame.ts blames the card whose recent tool call explains it: a command that stops Weft, or the last edit to the file that doesn't build. It never blames an edit just for being near a reload. When nothing on the board explains it, no card is marked. The usual cause is now refused outright: mcp__devservers__start pnpm dev in Weft's own folder (whose dev script frees the board's port) restarted Weft seven times in half an hour on 2026-10-23, and startDev now won't start it (lib/devserver.ts startsWeft). The Bash restart guard also catches pnpm dev without run (lib/restart-guard.ts). Drawn by components/CardMarks.tsx and components/CardView.tsx; docs/architecture/error-boundaries.md.
  • Puku's gateway is one click away in Weft's Goose settings. The Anthropic provider takes a base URL and a typed model id now, so a gateway that speaks the Anthropic Messages API is reachable from Settings → Models → Bring your own key: the base URL rides into Goose's env as ANTHROPIC_HOST (the host, with no trailing /v1 — Goose appends v1/messages itself), and the model field is a text box with the known ids as suggestions, because a gateway behind Anthropic's API serves model ids of its own. The Puku button beside the Provider picker sets all three at once — the Anthropic provider, https://api-cli.puku.sh and puku-ai-2.8 — in a single save, so all that is left is your Puku key. The model and the endpoint the Settings pane shows now actually reach the turn: the model becomes --model, and the endpoint you picked is the one the key's grant hands to Goose. Files: lib/goose-provider.ts (PUKU_GOOSE, the catalog's extraEnv and freeModel), electron/goose-provider.cjs, electron/goose-ipc.cjs, lib/goose-launch.ts, lib/runner-state.ts, lib/use-goose-selection.ts (setAll), components/GooseProviderKey.tsx, components/GooseKeyRow.tsx, docs/architecture/goose.md.
  • Goose is a real backend now, and its harness can improve itself. A Goose turn leaves its recipe on disk at `~/.weft/go...
Read more

Weft 0.6.0

Choose a tag to compare

@fotoflo fotoflo released this 25 Oct 10:00

Everything since 0.4.0 (0.5.0 was never published on its own). Signed and notarized: it opens without a Gatekeeper warning.

Added

  • Shared mini apps unfurl as themselves. A public app link now shows its own title, a one-line description and a branded card in Slack, X or iMessage, instead of "Shared app · Weft". The description comes from the app's weft-app.json, else the page's own meta tags, else a sentence Haiku writes from the page. The card has the app's glyph, name, description and owner, with a screenshot of the app taken in a hidden window when you share it. An app or page that names its own picture uses that instead. The Share dialog shows the card, lets you rewrite the description and has Refresh preview. Private, email and password links keep Weft's generic card, so nothing about the app leaks (lib/app-preview.ts, lib/app-preview-capture.ts, lib/app-share-meta.ts, lib/app-card-image.tsx, electron/page-snapshot.cjs, components/ShareAppPreview.tsx, cloud_app_shares.preview).
  • Every tool drawer explains itself, once. The first time you open a drawer on the left rail — 🔐 secrets, 🪝 webhooks, ▲ Vercel, Supabase, Cloudflare, 🚀 BlastCP, GitHub, ✦ skills, 🖥 services — or the code quality dialog or the leagues, a short narrated video plays at the top of it: what it's for and what it can do. Eleven intros, one page each in public/intros/, voiced by pnpm intros:narrate (Kokoro, the same local voice as the welcome tour, with its pad). ▶ beside the title plays it again, ↻ Replay appears over the frame once it ends, 🔊 shares the welcome tour's mute, and ✕ closes it. Each plays once and remembers that in localStorage; Settings → General's Reset tips plays them all again. It stands down for reduced motion, and a browser that refuses storage just plays them again (lib/feature-intros.ts, lib/use-feature-intro.ts, components/FeatureIntro.tsx, docs/architecture/feature-intros.md).
  • Weft's plans include BlastCP usage. weft.zip's pricing lists the BlastCP tier each plan comes with: Free has BlastCP Free (1 brand, 3 channels, 30 posts a month, $0.50 of AI generation once), and Pro and Team have BlastCP Creator (every channel, unlimited posts, $3 a month of AI generation). The tiers come from the catalog (plan.blastcp in lib/billing/plans.ts). BlastCP doesn't apply them yet.
  • The limits chart looks back in a new week. In the first 3 days after a weekly reset, the status strip's 5hr / 7d hover also shows the last 3 days of the week before (shaded, labeled "last week", their weekly bars dimmed), so a fresh reset no longer leaves the chart empty (lib/limit-chart.ts, components/LimitBurndown.tsx).
  • weft.zip plays the narrated tour. Under the board screenshot, "Take the tour, sound on." plays public/brand/weft-tour.mp4 (the welcome tour pnpm welcome:render makes), with its Welcome to Weft title card as the poster (public/brand/weft-tour-poster.webp) and nothing loaded until play (components/home/Tour.tsx).
  • BlastCP connects with your Weft account: no browser, an account made for you the first time, and a sign-in that renews itself (an RFC 8693 token exchange; BlastCP's side is its own change).
  • Anonymous setup stats from the welcome. Nine onboarding events (started, each tour beat, mute, sign-in, CLI log in, each step, the projects answer, the first thread, skip) go to PostHog through weft.zip, so we can see where people drop off. Only allow-listed events and props, never prompts, project names, paths or code; the ID is a hash of the device ID. On by default with a switch in Settings → Security (lib/telemetry*.ts, app/api/telemetry, components/StatsSwitch.tsx, docs/architecture/analytics.md).
  • Step through the welcome tour a beat at a time. A bar under Get ready's tour names the beat on screen ("3 of 9 · Connect your tools"), with a segment per beat to jump to, ‹ Back and Next › one beat at a time, and the mute; the voiceover picks up where a jump lands (components/WelcomeTourBar.tsx, public/welcome/live.js). The beats now run build → ship → show: tools, panes, code quality, BlastCP, the Wefter Profile, open source. The anonymous-stats switch left Get ready; it stays in Settings → Security.
  • The welcome tour shows BlastCP and says Weft is open source. Two new beats before "Now let's get started": BlastCP built in, your social accounts connecting and a launch post getting its image, video and voice before it's scheduled ("so you can build in public"); then the aimhuge/weft repo and a pull request going Open → Merged, voiced "If something is broken, or you want to add a feature, please feel free to make a pull request" (public/welcome/beats.html, now 54.5 s; the tour is 96 s).
  • Plans on the home page, and a Team tier. weft.zip now prices itself between the pillars and the FAQ: Free, Pro and Team read straight from the billing catalog, so a price change moves the page (components/home/Plans.tsx). Team is $9 per seat a month with Pro's allowance, and teams are now a paid capability — a Free workspace is a team of one and can't invite members (lib/billing/plan-gate.ts, lib/team-ops.ts runInvite). The gate fails closed: an unreadable or unpaid subscription reads as Free.
  • The welcome's Get ready screen plays how Weft works. It opens on a "Welcome to Weft" title naming the agents it runs on (Claude Code, Codex, Puku AI; public/welcome/intro.html) and ends on "Now let's get started", after which the next steps say "Allow Weft to see your projects" and "Now start a new thread". A 30-second loop with your own project names on the cards: a prompt starts a card, it asks you a question in Needs you, gets a key from the 🔐 vault after Touch ID without ever seeing it, shows its work in the browser pane, and lands in Done, which commits, refactors and deploys. It's a HyperFrames composition that plays live in the welcome and renders to an MP4 for weft.zip (public/welcome/loop.html, components/WelcomeLoop.tsx, pnpm welcome:render). Reduced motion keeps the still screenshot. After it the welcome plays a second composition, connect your tools, the Wefter Profile and code quality with the refactor scout (public/welcome/beats.html, handed off by public/welcome/live.js), with a beat on the pane: a thread's page as a live dashboard on vault keys and your data, shared as a weft.zip link; during the profile beat a real Sign in with GitHub button sits over it, and once you are signed in the beat shows your name and avatar. It plays over Weft's own scene pictures with a soft backing pad under the voice, and it is narrated, on by default with a mute button: pnpm welcome:narrate voices each caption with Kokoro, a local text-to-speech model, into one track per page (scripts/narrate-welcome.mjs), and pnpm welcome:render bakes the voice into a 78-second public/brand/weft-tour.mp4 for weft.zip.
  • Settings has a Security page. The default permission mode (from Threads), trusted folders (from General) and sign-in expiry (from Accounts) now sit together (components/SettingsSecurityPage.tsx).
  • The project bar's More menu has a filter. It has focus when the menu opens, matches the folder name and its ~ path, and Enter picks the top match. It's the same field and rule as the 🔐 and skills filters, now shared (components/SearchField.tsx, lib/text-filter.ts).
  • Replay the welcome from your avatar's menu. It moved out of Settings → General (which keeps Test it and Reset tips) into the avatar menu and the command palette (components/AccountButton.tsx).
  • The 🔐 drawer shows each vault's Vercel project. A vault whose folder is linked in ▲ gets a line under its name: the project, how many values are still to push, and Push to Vercel (one Touch ID) (components/VaultVercel.tsx).
  • The ▲ Vercel drawer's 🔐 list has a filter. Past five variables, a name search and a menu (To push, On Vercel, Vercel only, No value, with counts) narrow the list (components/VercelEnvRows.tsx, lib/vercel-env-filter.ts).
  • weft.zip measures the whole site and the download. GA4 now covers every weft.zip page (the FAQ, profiles, share links and the web board, not just the home), and PostHog runs alongside it. The download button sends download_clicked with its version to both, and New thread, replies, new projects and invites send their events through one track() (lib/analytics.ts, docs/architecture/analytics.md).
  • The ▲ Vercel drawer's 🔐 list has a filter once it runs past five variables: a name search and a menu for To push, On Vercel, Vercel only and No value.
  • Threads ask the CLI for a 1-hour prompt cache (CLAUDE_CODE_PROMPT_CACHE_TTL), so a short break no longer re-sends the whole thread.
  • Send fresh replaces "Continue in new card": a cold thread over 30k tokens sends your message to a new session in the same card, started from a brief (a Haiku summary written ahead of time, what's still open, and the diff of this thread's own edits) instead of re-sending the history; "Send with full history" is in the ▾ menu, and a divider in the thread shows the brief.
  • An idle reply box has a Done button that sends /weft:done (fresh when the cache is cold).
  • Stop is faster: it SIGTERMs the commands a thread is running at once, and ends the CLI after 1.5s (was 5s) if the interrupt is ignored.
  • Errors and Restart errors filters on the Review column. Two chips in its header, each on its own: Errors keeps cards whose last line is a thread or build error, Restart errors keeps cards that hit the 5-hour session limit. An active chip shows its count; both off after a reload (components/ReviewFilterChips.tsx, lib/board-view.ts).
  • The reply box's button says what Enter does. One mode at a time, named on a chip in the box and on the primary: Send, Queue (while a turn runs), Answer, Run (a ! command, in green with the folder), Replace (after ↑, in amber with the file edits that s...
Read more

Weft 0.4.0

Choose a tag to compare

@fotoflo fotoflo released this 17 Oct 16:55

Added

  • Software Update window. Weft → Check for Updates… opens a window instead of a message box: this Mac's version, the newest, when it last checked, the release notes, and one button for the next step (Check Now, Download…, Restart to Update). A missing feed reads as "No Weft release is published on GitHub yet" (electron/update-window.cjs, electron/update-view.cjs).

  • Context and limits stay in view in Review and New thread. A Review card's status strip (ctx, cache, tokens, 5hr/7d) now shows under the review panel instead of vanishing with the reply box, and the New thread dialog shows the folder and the picked CLI's 5hr/7d limits, which the board's footer under it used to hide (components/ThreadView.tsx, components/NewCardDialog.tsx, planLimitsFor in lib/board-view.ts).

  • A scene on first launch. With no Appearance chosen yet, the board opens on a scene instead of plain System: Twin Suns when the Mac is in dark mode, Salt Flats in light mode, and keeps it until you pick another (FIRST_SCENES in lib/theme.ts).

  • Prerender check. What only next build refuses under cacheComponents now fails pnpm test and is caught at edit time: a route segment config export (dynamic, revalidate, fetchCache, dynamicParams) is refused, and await params/searchParams/cookies()/headers() in a page's default export or generateMetadata outside <Suspense> gets a warning (lib/prerender-check.ts, docs/architecture/thread-guards.md).

  • Copilot personas. The copilot's name in its lane header picks who it is: Wingman (runs the board, as before), Founder (tests the business first: competitors, buyers, pricing, unit economics; after founder-skill) or Captain (runs a think → plan → build → review → test → ship sprint; after gstack). Each has its own picture; switching starts a new chat (lib/copilot-personas.ts).

  • Share a card. Share… on a card makes a weft.zip/s/<id> link that shows its thread and its browser pane side by side, live: private to the workspace, public, behind an email or a password (@aimhuge/share). Shown pages run sandboxed, a localhost page shows as its latest capture, key-shaped text is masked and long tool outputs aren't shared (docs/architecture/sharing.md).

  • Threads keep track of their subagents. Weft now reads the task events the CLI sends for every subagent and background command (started, progress, finished, the live set) and keeps them on the card: the thread panel shows a line above the reply box ("3 subagents running · 2 done") that opens to each one's elapsed time, tool count and latest line, and the card's footer counts the running ones (⑂3). An idle CLI with a background task running is no longer closed, which used to kill the task with it. When a CLI does end (a Weft restart), its running tasks are marked cut off and the thread is told on your next message, so it stops waiting for reports that will never come (lib/tasks.ts, lib/task-ledger.ts, components/TaskStrip.tsx).

  • Stop a thread's subagent or background command. Each running row in the thread's task strip has a ■ Stop, which asks the CLI to end just that task (the SDK's stopTask); a task whose CLI is already gone is marked stopped (lib/task-stop.ts, app/api/cards/[id]/tasks/[taskId]/stop).

  • Dev servers have their own list. The rail's 🖥 drawer lists every dev server for the board, with how long it has run, its url (opens in the pane), Restart and Stop; the icon counts the running ones. Threads start servers through Weft with mcp__devservers__start (and /weft:preview does) instead of Bash in the background, so a server outlives its thread's CLI and no longer keeps it open. Servers a thread still starts the old way show in the drawer too, and leave the thread's task strip (lib/devserver.ts, lib/devserver-tools.ts, components/DevServersDrawer.tsx).

  • Drag files from Finder anywhere in Weft. Drop on a card to open it with the files in its reply box, on an open thread (or the copilot) to attach them there, or on empty board to start a new thread with them; a pill says where they'll go. A file let go outside a composer no longer navigates the board away (lib/file-drop.ts, lib/use-file-drop.ts, components/FileDrop.tsx).

  • Weft asks before a thread leaves its project. A file tool or Bash command that reaches a folder outside the card's project (say ~/Downloads) now stops at a permission prompt with an Outside project chip, whether or not macOS would have refused it: Allow once, This card (its read scope) or Always (every card, settings.allowedFolders). Granted folders skip the CLI's own working-directory prompt for reads (lib/folder-guard.ts).

  • Threads can ask macOS for Downloads, Documents or Desktop. A thread got "Operation not permitted" reading ~/Downloads (say a new audio file to transcribe) because macOS blocks the folder until Weft is allowed. Now it calls mcp__board__request_folder_access (after such a refusal, or when you say to use files there; never on launch): macOS shows its own allow prompt the first time, and if the answer was already No a dialog explains it and Open Settings goes to Privacy & Security → Files and Folders (electron/folder-access.cjs, lib/folder-access-tool.ts). The packaged app carries NSDownloadsFolderUsageDescription.

  • A fresh coat of paint for the weft.zip dashboard. The workspace pages (Team, Billing, the web board) get a sidebar with the workspace switcher, the current page lit, and your account at the foot (a top bar on a phone); raised surfaces, stat tiles and avatars on Team, a plan card, usage glow and price tiles on Billing; the Weft mark and a spinner on the /team, /app and invite cards; a lighter admin bar. Weft's own photography runs through it: the speeder crossing the dunes at the sidebar's foot, a road under each page's title (Coast Road on Team, Canyon on Billing) and the twin-suns scene behind the sign-in-style cards (public/brand/scenes, copies of the board's theme pictures). Team's banner is a generated render of a crew of mechanics in a garage with the speeder (public/brand/scenes/garage.jpg), and Billing's invoices are tickets with a stub, a status glow and a scan stripe, and each opens its own full invoice page (/<workspace>/billing/invoices/<number>: the speeder at sunset, a status stamp, line items with proration and credits, a gradient total, links to the Stripe PDF and hosted page; Print makes a PDF too). One self-contained stylesheet (components/team/team.css, root .wz-app), so a direct hit on /team or an invite link no longer depends on the home page's CSS having loaded.

  • A new copilot chat suggests what to ask from everything Weft knows about your work (open cards in full, the uncommitted pile, quality and scout, streak, blanks, Vercel, Supabase, GitHub), most urgent first, above seven new starters ("Help me get started with Weft", …); the chips refresh in the background and wrap to fit the lane. Each chip is labeled with its project, so the chat shows only the selected project's chips (plus account-wide ones), and switching projects filters at once without a new run.

  • The copilot onboards you: it knows what isn't linked yet (Weft account, GitHub CLI, Vercel, Supabase, Cloudflare, analytics), offers the one link that helps, and points you at the right drawer.

  • License: FSL-1.1-ALv2. Weft moves from Apache-2.0 to the Functional Source License: fork, change, self-host and use it at work, but don't ship it as a competing product or service. Each release becomes Apache-2.0 two years after it ships; v0.3.0 and earlier stay Apache-2.0. New TRADEMARKS.md: forks you share need their own name and icon.

  • Terms of Service at weft.zip/terms. What the online services cover (accounts, workspaces, sync, the relay, plans), fair use (no scraping, no unofficial clients or forks calling Weft's servers, no cheating for streaks or stretching free limits), and that your content stays yours. Linked from the home page footer.

  • Readable edit prompts. A held Edit or Write (the symbol-claim "Wait, or go ahead?" or any other permission prompt) shows who asks, the file, the function it changes, +added −removed and a short diff with real line breaks, instead of escaped JSON, and says why it asked as a category (Same code, Overlapping code, Whole file, or Permission mode) with the card holding the code. When another card holds the code, Wait comes first: the edit stays held until that card finishes or commits, then the thread re-reads the file and redoes the edit on top. Raw still shows the JSON (lib/edit-preview.ts, components/PermissionInput.tsx).

  • Import guards read the project's tsconfig paths. The route-import guard and the after-edit import check used to treat @/x as the project root's x, so in DeckCP (@/* → ./src/*) a valid src/app/**/page.tsx import was refused. Aliases now come from the edited file's project tsconfig.json (compilerOptions.paths against baseUrl, a relative extends followed), and @/ with no mapping tries the root, then src/ (lib/ts-paths.ts).

  • Dropped-import guard. An edit that removes an import the file still uses (the board's "columnMinWidth is not defined") is reported to the thread right away (lib/dropped-imports.ts). The .claude/settings.json guards now also run for a puku-cli -p or claude -p a thread starts, which used to skip them (lib/weft-thread.ts).

  • New thread on the weft.zip board. Pick one of your synced folders, write a prompt and your Mac makes the card and runs it, then the board opens it. The request is a cloud_commands row the Mac claims over Realtime (and a 30 s check); it only obeys your own account's requests for a folder switched on in Settings → Sync, and uses its own permission setting. POST /api/cards and the inbox now share lib/new-card.ts.

  • Settings → Models → Review all picks the Claude model the Revi...

Read more