Repository files navigation

iterate tasks

A Kanban board over the tasks/ folder of any iterate project's config repo — deployed as a stateless vessel at tasks.iterate.workers.dev. It never holds project secrets or user sessions of its own. Every useful request arrives through a reverse proxy in the project's /repos/config worker on a host like tasks--<slug>.iterate.app.

Per connection the vessel:

  1. Reads the trusted x-itx-project-id header and the platform's iterate-project-auth cookie (forwarded by the proxy).
  2. Opens a Cap'n Web WebSocket to os.iterate.com/api and authenticates with { type: "project-app-session", token }.
  3. Reads/writes tasks/ markdown in /repos/config via listTaskFiles / commitFiles — the git history of the config repo is the board's history, attributed to the connected user.

The board is a collaborative checkout: loading / creates one and redirects to its shareable /c/<checkout-id> URL. A checkout is a y-partyserverYServer Durable Object (named <projectId>:<checkoutId>) holding an in-DO working copy of the repo's task files as one Yjs doc — a files map of path → Y.Text plus a meta map with the base commit it was seeded from at first join. Everyone on the link edits the same doc over the stock y-protocols WebSocket wire (/api/checkout/<id>): drags, adds, deletes, and each keystroke in the CodeMirror task editor sync live, with presence chips and named, colored remote cursors (y-codemirror.next + awareness). The doc persists as one update blob in DO storage (debounced onSave), so a checkout survives eviction.

Changed cards wear an A/M mark against the base commit, pending deletions stay visible (and reversible) in a strip above the board, and the Commit button — or a 60s idle autosave — POSTs to the DO, which flushes the doc's diff against base as ONE commitFiles batch attributed to the poster: typed message, AI-generated one (/generate-message via ai.run), or a deterministic summary. The new base syncs back through the doc, clearing everyone's marks. External commits to the repo are ignored while a checkout lives — start a fresh checkout to pick up a new HEAD.

There is no pairing form, no OIDC client, and no capability door. The only DO state is the Yjs blob; auth is per-join and per-op (the token is verified by using it against the platform).

Using it

Add a tasks app branch to the project's config worker.ts that gates on project membership and reverse-proxies this vessel:

// tasks app — reverse-proxy the vessel at tasks.iterate.workers.devexportclassTasksAppextendsIterateWorkerEntrypoint{asyncfetch(req: Request): Promise<Response>{
using itx=awaitthis.env.ITX.get();// (a) project-member auth gate — return its response when non-nullconstauth=awaititx.auth.get({policy: "project-member"}).fetch(req);if(auth)returnauth;// (b) transparent proxy: pages, assets, and WebSocket upgrades.// The kv knob points the proxy at a dev tunnel instead of the deployed// vessel (see "Developing against a live project" in the tasks repo's// README); absent knob means production behavior.constdescription=awaititx.__describe();consturl=newURL(req.url);url.protocol="https:";url.host=(awaititx.kv.get("tasks-app-origin"))??"tasks.iterate.workers.dev";constheaders=newHeaders(req.headers);headers.set("x-itx-project-id",description.projectId);returnfetch(newRequest(url,{method: req.method,
headers,body: req.body,redirect: "manual",}));}}

Wire that class into the project's app router the same way as the seeded HelloApp / InternalApp examples. Then open https://tasks--<slug>.iterate.app/ — sign-in is the platform's project- member gate; the board UI is this app at /.

Drag cards, add tasks, click a card to edit its markdown together — everyone on the checkout link sees the same board, each other's chips, and each other's cursors in the editor. Commit from the Commit button (the ▾ panel reviews the change set, writes an AI commit message, or discards everything), or let the 60s idle autosave commit with a generated summary. A checkout is pinned to the base commit it was seeded from; / always starts a fresh one from HEAD.

Hitting tasks.iterate.workers.dev directly serves only a landing page with the same proxy snippet — no project context, no board.

Development

pnpm install
cp .dev.vars.example .dev.vars
pnpm dev

.dev.vars.example points OS_BASE_URL at https://os.iterate.com — the develop-against-production loop below, which is the loop you usually want. Point it at a local os dev server (http://localhost:<port>) to run fully local instead. Either way the vessel does not mint sessions: requests need the x-itx-project-id header and a valid iterate-project-auth cookie stamped on them (a project's proxy does this; headless, use scripts/probe-board-authed.mjs).

Developing against a live project

The vessel is stateless and auth rides with each connection (the proxy stamps the project header and forwards the user's cookie; os verifies the token), so "deployed vessel" vs "your laptop" is just a hostname swap in the project's proxy. Run local dev behind a captun tunnel and flip the project's tasks-app-origin kv knob at it — you get platform login as yourself, real project data, every commit attributed to you, but the app code is your local checkout with HMR. Full guide: the platform's remote-apps doc.

Prerequisite: the project's config worker reads the knob with the deployed host as fallback, as in the proxy snippet above.

The daily loop, two commands:

# 1. in this repo — local vite dev, publicly tunneled (HTTP + WebSocket):
CAPTUN_TUNNEL_NAME=me-tasks \
CAPTUN_TOKEN=$(doppler secrets get CAPTUN_TOKEN --plain --project _shared --config dev) \
pnpm dev
# 2. point the project at your tunnel (from the monorepo's apps/os):
doppler run --config prd -- pnpm cli itx run --context <project-id> \
-e 'await itx.kv.set("tasks-app-origin", "me-tasks.tunnels.iterate.com")'

Then open https://tasks--<slug>.iterate.app in a normal browser. Flip back with itx.kv.delete("tasks-app-origin") — absent knob means the deployed vessel, byte-identical to production.

Know before you dogfood:

  • Prefer the per-user variant (in the guide): the project-wide knob routes every member's traffic — including their session cookies — to your laptop while it is set. Per-user routing sends only your own sessions to the tunnel; everyone else stays on the deployed vessel.
  • Commits are real. The board's 60s idle autosave turns test drags into actual commits on the project's config repo, attributed to you. Revertable via git, but be conscious of it on a production project.
  • Live agents orphan local edits. The board re-reads HEAD every 30s; a HEAD moved by an agent or another member discards your uncommitted working-tree edits (by design — see above).
  • The tunnel exposes vite dev, not project data. Direct hits on the tunnel get only the landing page, and a forged project header without a valid cookie dies at os. What is public is the dev server itself (this checkout's source, HMR endpoints) — fine here, but don't reuse the pattern for a repo with secrets in the checkout.
  • OS_BASE_URL must be https://os.iterate.com (the committed default) — the forwarded production token means nothing to a local os.

Dev-mode through any proxy needs server.allowedHosts (set in vite.config.ts) — without it, vite 403s proxied Hosts and hydration fails in ways that look like framework bugs.

Deployment

pnpm run deploy # vite build && wrangler deploy

No secrets. The only var is OS_BASE_URL (defaults to https://os.iterate.com in wrangler.jsonc).

About

Kanban task board for any iterate project's /repos/config — a standalone TanStack Start app on Cloudflare Workers, mutually authenticated with os.iterate.com

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

iterate tasks

A Kanban board over the tasks/ folder of any iterate project's config repo — deployed as a stateless vessel at tasks.iterate.workers.dev. It never holds project secrets or user sessions of its own. Every useful request arrives through a reverse proxy in the project's /repos/config worker on a host like tasks--<slug>.iterate.app.

Per connection the vessel:

  1. Reads the trusted x-itx-project-id header and the platform's iterate-project-auth cookie (forwarded by the proxy).
  2. Opens a Cap'n Web WebSocket to os.iterate.com/api and authenticates with { type: "project-app-session", token }.
  3. Reads/writes tasks/ markdown in /repos/config via listTaskFiles / commitFiles — the git history of the config repo is the board's history, attributed to the connected user.

The board is a collaborative checkout: loading / creates one and redirects to its shareable /c/<checkout-id> URL. A checkout is a y-partyserverYServer Durable Object (named <projectId>:<checkoutId>) holding an in-DO working copy of the repo's task files as one Yjs doc — a files map of path → Y.Text plus a meta map with the base commit it was seeded from at first join. Everyone on the link edits the same doc over the stock y-protocols WebSocket wire (/api/checkout/<id>): drags, adds, deletes, and each keystroke in the CodeMirror task editor sync live, with presence chips and named, colored remote cursors (y-codemirror.next + awareness). The doc persists as one update blob in DO storage (debounced onSave), so a checkout survives eviction.

Changed cards wear an A/M mark against the base commit, pending deletions stay visible (and reversible) in a strip above the board, and the Commit button — or a 60s idle autosave — POSTs to the DO, which flushes the doc's diff against base as ONE commitFiles batch attributed to the poster: typed message, AI-generated one (/generate-message via ai.run), or a deterministic summary. The new base syncs back through the doc, clearing everyone's marks. External commits to the repo are ignored while a checkout lives — start a fresh checkout to pick up a new HEAD.

There is no pairing form, no OIDC client, and no capability door. The only DO state is the Yjs blob; auth is per-join and per-op (the token is verified by using it against the platform).

Using it

Add a tasks app branch to the project's config worker.ts that gates on project membership and reverse-proxies this vessel:

// tasks app — reverse-proxy the vessel at tasks.iterate.workers.devexportclassTasksAppextendsIterateWorkerEntrypoint{asyncfetch(req: Request): Promise<Response>{
using itx=awaitthis.env.ITX.get();// (a) project-member auth gate — return its response when non-nullconstauth=awaititx.auth.get({policy: "project-member"}).fetch(req);if(auth)returnauth;// (b) transparent proxy: pages, assets, and WebSocket upgrades.// The kv knob points the proxy at a dev tunnel instead of the deployed// vessel (see "Developing against a live project" in the tasks repo's// README); absent knob means production behavior.constdescription=awaititx.__describe();consturl=newURL(req.url);url.protocol="https:";url.host=(awaititx.kv.get("tasks-app-origin"))??"tasks.iterate.workers.dev";constheaders=newHeaders(req.headers);headers.set("x-itx-project-id",description.projectId);returnfetch(newRequest(url,{method: req.method,
headers,body: req.body,redirect: "manual",}));}}

Wire that class into the project's app router the same way as the seeded HelloApp / InternalApp examples. Then open https://tasks--<slug>.iterate.app/ — sign-in is the platform's project- member gate; the board UI is this app at /.

Drag cards, add tasks, click a card to edit its markdown together — everyone on the checkout link sees the same board, each other's chips, and each other's cursors in the editor. Commit from the Commit button (the ▾ panel reviews the change set, writes an AI commit message, or discards everything), or let the 60s idle autosave commit with a generated summary. A checkout is pinned to the base commit it was seeded from; / always starts a fresh one from HEAD.

Hitting tasks.iterate.workers.dev directly serves only a landing page with the same proxy snippet — no project context, no board.

Development

pnpm install
cp .dev.vars.example .dev.vars
pnpm dev

.dev.vars.example points OS_BASE_URL at https://os.iterate.com — the develop-against-production loop below, which is the loop you usually want. Point it at a local os dev server (http://localhost:<port>) to run fully local instead. Either way the vessel does not mint sessions: requests need the x-itx-project-id header and a valid iterate-project-auth cookie stamped on them (a project's proxy does this; headless, use scripts/probe-board-authed.mjs).

Developing against a live project

The vessel is stateless and auth rides with each connection (the proxy stamps the project header and forwards the user's cookie; os verifies the token), so "deployed vessel" vs "your laptop" is just a hostname swap in the project's proxy. Run local dev behind a captun tunnel and flip the project's tasks-app-origin kv knob at it — you get platform login as yourself, real project data, every commit attributed to you, but the app code is your local checkout with HMR. Full guide: the platform's remote-apps doc.

Prerequisite: the project's config worker reads the knob with the deployed host as fallback, as in the proxy snippet above.

The daily loop, two commands:

# 1. in this repo — local vite dev, publicly tunneled (HTTP + WebSocket):
CAPTUN_TUNNEL_NAME=me-tasks \
CAPTUN_TOKEN=$(doppler secrets get CAPTUN_TOKEN --plain --project _shared --config dev) \
pnpm dev
# 2. point the project at your tunnel (from the monorepo's apps/os):
doppler run --config prd -- pnpm cli itx run --context <project-id> \
-e 'await itx.kv.set("tasks-app-origin", "me-tasks.tunnels.iterate.com")'

Then open https://tasks--<slug>.iterate.app in a normal browser. Flip back with itx.kv.delete("tasks-app-origin") — absent knob means the deployed vessel, byte-identical to production.

Know before you dogfood:

  • Prefer the per-user variant (in the guide): the project-wide knob routes every member's traffic — including their session cookies — to your laptop while it is set. Per-user routing sends only your own sessions to the tunnel; everyone else stays on the deployed vessel.
  • Commits are real. The board's 60s idle autosave turns test drags into actual commits on the project's config repo, attributed to you. Revertable via git, but be conscious of it on a production project.
  • Live agents orphan local edits. The board re-reads HEAD every 30s; a HEAD moved by an agent or another member discards your uncommitted working-tree edits (by design — see above).
  • The tunnel exposes vite dev, not project data. Direct hits on the tunnel get only the landing page, and a forged project header without a valid cookie dies at os. What is public is the dev server itself (this checkout's source, HMR endpoints) — fine here, but don't reuse the pattern for a repo with secrets in the checkout.
  • OS_BASE_URL must be https://os.iterate.com (the committed default) — the forwarded production token means nothing to a local os.

Dev-mode through any proxy needs server.allowedHosts (set in vite.config.ts) — without it, vite 403s proxied Hosts and hydration fails in ways that look like framework bugs.

Deployment

pnpm run deploy # vite build && wrangler deploy

No secrets. The only var is OS_BASE_URL (defaults to https://os.iterate.com in wrangler.jsonc).

About

Kanban task board for any iterate project's /repos/config — a standalone TanStack Start app on Cloudflare Workers, mutually authenticated with os.iterate.com

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

iterate tasks

A Kanban board over the tasks/ folder of any iterate project's config repo — deployed as a stateless vessel at tasks.iterate.workers.dev. It never holds project secrets or user sessions of its own. Every useful request arrives through a reverse proxy in the project's /repos/config worker on a host like tasks--<slug>.iterate.app.

Per connection the vessel:

  1. Reads the trusted x-itx-project-id header and the platform's iterate-project-auth cookie (forwarded by the proxy).
  2. Opens a Cap'n Web WebSocket to os.iterate.com/api and authenticates with { type: "project-app-session", token }.
  3. Reads/writes tasks/ markdown in /repos/config via listTaskFiles / commitFiles — the git history of the config repo is the board's history, attributed to the connected user.

The board is a collaborative checkout: loading / creates one and redirects to its shareable /c/<checkout-id> URL. A checkout is a y-partyserverYServer Durable Object (named <projectId>:<checkoutId>) holding an in-DO working copy of the repo's task files as one Yjs doc — a files map of path → Y.Text plus a meta map with the base commit it was seeded from at first join. Everyone on the link edits the same doc over the stock y-protocols WebSocket wire (/api/checkout/<id>): drags, adds, deletes, and each keystroke in the CodeMirror task editor sync live, with presence chips and named, colored remote cursors (y-codemirror.next + awareness). The doc persists as one update blob in DO storage (debounced onSave), so a checkout survives eviction.

Changed cards wear an A/M mark against the base commit, pending deletions stay visible (and reversible) in a strip above the board, and the Commit button — or a 60s idle autosave — POSTs to the DO, which flushes the doc's diff against base as ONE commitFiles batch attributed to the poster: typed message, AI-generated one (/generate-message via ai.run), or a deterministic summary. The new base syncs back through the doc, clearing everyone's marks. External commits to the repo are ignored while a checkout lives — start a fresh checkout to pick up a new HEAD.

There is no pairing form, no OIDC client, and no capability door. The only DO state is the Yjs blob; auth is per-join and per-op (the token is verified by using it against the platform).

Using it

Add a tasks app branch to the project's config worker.ts that gates on project membership and reverse-proxies this vessel:

// tasks app — reverse-proxy the vessel at tasks.iterate.workers.devexportclassTasksAppextendsIterateWorkerEntrypoint{asyncfetch(req: Request): Promise<Response>{
using itx=awaitthis.env.ITX.get();// (a) project-member auth gate — return its response when non-nullconstauth=awaititx.auth.get({policy: "project-member"}).fetch(req);if(auth)returnauth;// (b) transparent proxy: pages, assets, and WebSocket upgrades.// The kv knob points the proxy at a dev tunnel instead of the deployed// vessel (see "Developing against a live project" in the tasks repo's// README); absent knob means production behavior.constdescription=awaititx.__describe();consturl=newURL(req.url);url.protocol="https:";url.host=(awaititx.kv.get("tasks-app-origin"))??"tasks.iterate.workers.dev";constheaders=newHeaders(req.headers);headers.set("x-itx-project-id",description.projectId);returnfetch(newRequest(url,{method: req.method,
headers,body: req.body,redirect: "manual",}));}}

Wire that class into the project's app router the same way as the seeded HelloApp / InternalApp examples. Then open https://tasks--<slug>.iterate.app/ — sign-in is the platform's project- member gate; the board UI is this app at /.

Drag cards, add tasks, click a card to edit its markdown together — everyone on the checkout link sees the same board, each other's chips, and each other's cursors in the editor. Commit from the Commit button (the ▾ panel reviews the change set, writes an AI commit message, or discards everything), or let the 60s idle autosave commit with a generated summary. A checkout is pinned to the base commit it was seeded from; / always starts a fresh one from HEAD.

Hitting tasks.iterate.workers.dev directly serves only a landing page with the same proxy snippet — no project context, no board.

Development

pnpm install
cp .dev.vars.example .dev.vars
pnpm dev

.dev.vars.example points OS_BASE_URL at https://os.iterate.com — the develop-against-production loop below, which is the loop you usually want. Point it at a local os dev server (http://localhost:<port>) to run fully local instead. Either way the vessel does not mint sessions: requests need the x-itx-project-id header and a valid iterate-project-auth cookie stamped on them (a project's proxy does this; headless, use scripts/probe-board-authed.mjs).

Developing against a live project

The vessel is stateless and auth rides with each connection (the proxy stamps the project header and forwards the user's cookie; os verifies the token), so "deployed vessel" vs "your laptop" is just a hostname swap in the project's proxy. Run local dev behind a captun tunnel and flip the project's tasks-app-origin kv knob at it — you get platform login as yourself, real project data, every commit attributed to you, but the app code is your local checkout with HMR. Full guide: the platform's remote-apps doc.

Prerequisite: the project's config worker reads the knob with the deployed host as fallback, as in the proxy snippet above.

The daily loop, two commands:

# 1. in this repo — local vite dev, publicly tunneled (HTTP + WebSocket):
CAPTUN_TUNNEL_NAME=me-tasks \
CAPTUN_TOKEN=$(doppler secrets get CAPTUN_TOKEN --plain --project _shared --config dev) \
pnpm dev
# 2. point the project at your tunnel (from the monorepo's apps/os):
doppler run --config prd -- pnpm cli itx run --context <project-id> \
-e 'await itx.kv.set("tasks-app-origin", "me-tasks.tunnels.iterate.com")'

Then open https://tasks--<slug>.iterate.app in a normal browser. Flip back with itx.kv.delete("tasks-app-origin") — absent knob means the deployed vessel, byte-identical to production.

Know before you dogfood:

  • Prefer the per-user variant (in the guide): the project-wide knob routes every member's traffic — including their session cookies — to your laptop while it is set. Per-user routing sends only your own sessions to the tunnel; everyone else stays on the deployed vessel.
  • Commits are real. The board's 60s idle autosave turns test drags into actual commits on the project's config repo, attributed to you. Revertable via git, but be conscious of it on a production project.
  • Live agents orphan local edits. The board re-reads HEAD every 30s; a HEAD moved by an agent or another member discards your uncommitted working-tree edits (by design — see above).
  • The tunnel exposes vite dev, not project data. Direct hits on the tunnel get only the landing page, and a forged project header without a valid cookie dies at os. What is public is the dev server itself (this checkout's source, HMR endpoints) — fine here, but don't reuse the pattern for a repo with secrets in the checkout.
  • OS_BASE_URL must be https://os.iterate.com (the committed default) — the forwarded production token means nothing to a local os.

Dev-mode through any proxy needs server.allowedHosts (set in vite.config.ts) — without it, vite 403s proxied Hosts and hydration fails in ways that look like framework bugs.

Deployment

pnpm run deploy # vite build && wrangler deploy

No secrets. The only var is OS_BASE_URL (defaults to https://os.iterate.com in wrangler.jsonc).

About

Kanban task board for any iterate project's /repos/config — a standalone TanStack Start app on Cloudflare Workers, mutually authenticated with os.iterate.com

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

iterate tasks

A Kanban board over the tasks/ folder of any iterate project's config repo — deployed as a stateless vessel at tasks.iterate.workers.dev. It never holds project secrets or user sessions of its own. Every useful request arrives through a reverse proxy in the project's /repos/config worker on a host like tasks--<slug>.iterate.app.

Per connection the vessel:

  1. Reads the trusted x-itx-project-id header and the platform's iterate-project-auth cookie (forwarded by the proxy).
  2. Opens a Cap'n Web WebSocket to os.iterate.com/api and authenticates with { type: "project-app-session", token }.
  3. Reads/writes tasks/ markdown in /repos/config via listTaskFiles / commitFiles — the git history of the config repo is the board's history, attributed to the connected user.

The board is a collaborative checkout: loading / creates one and redirects to its shareable /c/<checkout-id> URL. A checkout is a y-partyserverYServer Durable Object (named <projectId>:<checkoutId>) holding an in-DO working copy of the repo's task files as one Yjs doc — a files map of path → Y.Text plus a meta map with the base commit it was seeded from at first join. Everyone on the link edits the same doc over the stock y-protocols WebSocket wire (/api/checkout/<id>): drags, adds, deletes, and each keystroke in the CodeMirror task editor sync live, with presence chips and named, colored remote cursors (y-codemirror.next + awareness). The doc persists as one update blob in DO storage (debounced onSave), so a checkout survives eviction.

Changed cards wear an A/M mark against the base commit, pending deletions stay visible (and reversible) in a strip above the board, and the Commit button — or a 60s idle autosave — POSTs to the DO, which flushes the doc's diff against base as ONE commitFiles batch attributed to the poster: typed message, AI-generated one (/generate-message via ai.run), or a deterministic summary. The new base syncs back through the doc, clearing everyone's marks. External commits to the repo are ignored while a checkout lives — start a fresh checkout to pick up a new HEAD.

There is no pairing form, no OIDC client, and no capability door. The only DO state is the Yjs blob; auth is per-join and per-op (the token is verified by using it against the platform).

Using it

Add a tasks app branch to the project's config worker.ts that gates on project membership and reverse-proxies this vessel:

// tasks app — reverse-proxy the vessel at tasks.iterate.workers.devexportclassTasksAppextendsIterateWorkerEntrypoint{asyncfetch(req: Request): Promise<Response>{
using itx=awaitthis.env.ITX.get();// (a) project-member auth gate — return its response when non-nullconstauth=awaititx.auth.get({policy: "project-member"}).fetch(req);if(auth)returnauth;// (b) transparent proxy: pages, assets, and WebSocket upgrades.// The kv knob points the proxy at a dev tunnel instead of the deployed// vessel (see "Developing against a live project" in the tasks repo's// README); absent knob means production behavior.constdescription=awaititx.__describe();consturl=newURL(req.url);url.protocol="https:";url.host=(awaititx.kv.get("tasks-app-origin"))??"tasks.iterate.workers.dev";constheaders=newHeaders(req.headers);headers.set("x-itx-project-id",description.projectId);returnfetch(newRequest(url,{method: req.method,
headers,body: req.body,redirect: "manual",}));}}

Wire that class into the project's app router the same way as the seeded HelloApp / InternalApp examples. Then open https://tasks--<slug>.iterate.app/ — sign-in is the platform's project- member gate; the board UI is this app at /.

Drag cards, add tasks, click a card to edit its markdown together — everyone on the checkout link sees the same board, each other's chips, and each other's cursors in the editor. Commit from the Commit button (the ▾ panel reviews the change set, writes an AI commit message, or discards everything), or let the 60s idle autosave commit with a generated summary. A checkout is pinned to the base commit it was seeded from; / always starts a fresh one from HEAD.

Hitting tasks.iterate.workers.dev directly serves only a landing page with the same proxy snippet — no project context, no board.

Development

pnpm install
cp .dev.vars.example .dev.vars
pnpm dev

.dev.vars.example points OS_BASE_URL at https://os.iterate.com — the develop-against-production loop below, which is the loop you usually want. Point it at a local os dev server (http://localhost:<port>) to run fully local instead. Either way the vessel does not mint sessions: requests need the x-itx-project-id header and a valid iterate-project-auth cookie stamped on them (a project's proxy does this; headless, use scripts/probe-board-authed.mjs).

Developing against a live project

The vessel is stateless and auth rides with each connection (the proxy stamps the project header and forwards the user's cookie; os verifies the token), so "deployed vessel" vs "your laptop" is just a hostname swap in the project's proxy. Run local dev behind a captun tunnel and flip the project's tasks-app-origin kv knob at it — you get platform login as yourself, real project data, every commit attributed to you, but the app code is your local checkout with HMR. Full guide: the platform's remote-apps doc.

Prerequisite: the project's config worker reads the knob with the deployed host as fallback, as in the proxy snippet above.

The daily loop, two commands:

# 1. in this repo — local vite dev, publicly tunneled (HTTP + WebSocket):
CAPTUN_TUNNEL_NAME=me-tasks \
CAPTUN_TOKEN=$(doppler secrets get CAPTUN_TOKEN --plain --project _shared --config dev) \
pnpm dev
# 2. point the project at your tunnel (from the monorepo's apps/os):
doppler run --config prd -- pnpm cli itx run --context <project-id> \
-e 'await itx.kv.set("tasks-app-origin", "me-tasks.tunnels.iterate.com")'

Then open https://tasks--<slug>.iterate.app in a normal browser. Flip back with itx.kv.delete("tasks-app-origin") — absent knob means the deployed vessel, byte-identical to production.

Know before you dogfood:

  • Prefer the per-user variant (in the guide): the project-wide knob routes every member's traffic — including their session cookies — to your laptop while it is set. Per-user routing sends only your own sessions to the tunnel; everyone else stays on the deployed vessel.
  • Commits are real. The board's 60s idle autosave turns test drags into actual commits on the project's config repo, attributed to you. Revertable via git, but be conscious of it on a production project.
  • Live agents orphan local edits. The board re-reads HEAD every 30s; a HEAD moved by an agent or another member discards your uncommitted working-tree edits (by design — see above).
  • The tunnel exposes vite dev, not project data. Direct hits on the tunnel get only the landing page, and a forged project header without a valid cookie dies at os. What is public is the dev server itself (this checkout's source, HMR endpoints) — fine here, but don't reuse the pattern for a repo with secrets in the checkout.
  • OS_BASE_URL must be https://os.iterate.com (the committed default) — the forwarded production token means nothing to a local os.

Dev-mode through any proxy needs server.allowedHosts (set in vite.config.ts) — without it, vite 403s proxied Hosts and hydration fails in ways that look like framework bugs.

Deployment

pnpm run deploy # vite build && wrangler deploy

No secrets. The only var is OS_BASE_URL (defaults to https://os.iterate.com in wrangler.jsonc).

About

Kanban task board for any iterate project's /repos/config — a standalone TanStack Start app on Cloudflare Workers, mutually authenticated with os.iterate.com

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

iterate tasks

A Kanban board over the tasks/ folder of any iterate project's config repo — deployed as a stateless vessel at tasks.iterate.workers.dev. It never holds project secrets or user sessions of its own. Every useful request arrives through a reverse proxy in the project's /repos/config worker on a host like tasks--<slug>.iterate.app.

Per connection the vessel:

  1. Reads the trusted x-itx-project-id header and the platform's iterate-project-auth cookie (forwarded by the proxy).
  2. Opens a Cap'n Web WebSocket to os.iterate.com/api and authenticates with { type: "project-app-session", token }.
  3. Reads/writes tasks/ markdown in /repos/config via listTaskFiles / commitFiles — the git history of the config repo is the board's history, attributed to the connected user.

The board is a collaborative checkout: loading / creates one and redirects to its shareable /c/<checkout-id> URL. A checkout is a y-partyserverYServer Durable Object (named <projectId>:<checkoutId>) holding an in-DO working copy of the repo's task files as one Yjs doc — a files map of path → Y.Text plus a meta map with the base commit it was seeded from at first join. Everyone on the link edits the same doc over the stock y-protocols WebSocket wire (/api/checkout/<id>): drags, adds, deletes, and each keystroke in the CodeMirror task editor sync live, with presence chips and named, colored remote cursors (y-codemirror.next + awareness). The doc persists as one update blob in DO storage (debounced onSave), so a checkout survives eviction.

Changed cards wear an A/M mark against the base commit, pending deletions stay visible (and reversible) in a strip above the board, and the Commit button — or a 60s idle autosave — POSTs to the DO, which flushes the doc's diff against base as ONE commitFiles batch attributed to the poster: typed message, AI-generated one (/generate-message via ai.run), or a deterministic summary. The new base syncs back through the doc, clearing everyone's marks. External commits to the repo are ignored while a checkout lives — start a fresh checkout to pick up a new HEAD.

There is no pairing form, no OIDC client, and no capability door. The only DO state is the Yjs blob; auth is per-join and per-op (the token is verified by using it against the platform).

Using it

Add a tasks app branch to the project's config worker.ts that gates on project membership and reverse-proxies this vessel:

// tasks app — reverse-proxy the vessel at tasks.iterate.workers.devexportclassTasksAppextendsIterateWorkerEntrypoint{asyncfetch(req: Request): Promise<Response>{
using itx=awaitthis.env.ITX.get();// (a) project-member auth gate — return its response when non-nullconstauth=awaititx.auth.get({policy: "project-member"}).fetch(req);if(auth)returnauth;// (b) transparent proxy: pages, assets, and WebSocket upgrades.// The kv knob points the proxy at a dev tunnel instead of the deployed// vessel (see "Developing against a live project" in the tasks repo's// README); absent knob means production behavior.constdescription=awaititx.__describe();consturl=newURL(req.url);url.protocol="https:";url.host=(awaititx.kv.get("tasks-app-origin"))??"tasks.iterate.workers.dev";constheaders=newHeaders(req.headers);headers.set("x-itx-project-id",description.projectId);returnfetch(newRequest(url,{method: req.method,
headers,body: req.body,redirect: "manual",}));}}

Wire that class into the project's app router the same way as the seeded HelloApp / InternalApp examples. Then open https://tasks--<slug>.iterate.app/ — sign-in is the platform's project- member gate; the board UI is this app at /.

Drag cards, add tasks, click a card to edit its markdown together — everyone on the checkout link sees the same board, each other's chips, and each other's cursors in the editor. Commit from the Commit button (the ▾ panel reviews the change set, writes an AI commit message, or discards everything), or let the 60s idle autosave commit with a generated summary. A checkout is pinned to the base commit it was seeded from; / always starts a fresh one from HEAD.

Hitting tasks.iterate.workers.dev directly serves only a landing page with the same proxy snippet — no project context, no board.

Development

pnpm install
cp .dev.vars.example .dev.vars
pnpm dev

.dev.vars.example points OS_BASE_URL at https://os.iterate.com — the develop-against-production loop below, which is the loop you usually want. Point it at a local os dev server (http://localhost:<port>) to run fully local instead. Either way the vessel does not mint sessions: requests need the x-itx-project-id header and a valid iterate-project-auth cookie stamped on them (a project's proxy does this; headless, use scripts/probe-board-authed.mjs).

Developing against a live project

The vessel is stateless and auth rides with each connection (the proxy stamps the project header and forwards the user's cookie; os verifies the token), so "deployed vessel" vs "your laptop" is just a hostname swap in the project's proxy. Run local dev behind a captun tunnel and flip the project's tasks-app-origin kv knob at it — you get platform login as yourself, real project data, every commit attributed to you, but the app code is your local checkout with HMR. Full guide: the platform's remote-apps doc.

Prerequisite: the project's config worker reads the knob with the deployed host as fallback, as in the proxy snippet above.

The daily loop, two commands:

# 1. in this repo — local vite dev, publicly tunneled (HTTP + WebSocket):
CAPTUN_TUNNEL_NAME=me-tasks \
CAPTUN_TOKEN=$(doppler secrets get CAPTUN_TOKEN --plain --project _shared --config dev) \
pnpm dev
# 2. point the project at your tunnel (from the monorepo's apps/os):
doppler run --config prd -- pnpm cli itx run --context <project-id> \
-e 'await itx.kv.set("tasks-app-origin", "me-tasks.tunnels.iterate.com")'

Then open https://tasks--<slug>.iterate.app in a normal browser. Flip back with itx.kv.delete("tasks-app-origin") — absent knob means the deployed vessel, byte-identical to production.

Know before you dogfood:

  • Prefer the per-user variant (in the guide): the project-wide knob routes every member's traffic — including their session cookies — to your laptop while it is set. Per-user routing sends only your own sessions to the tunnel; everyone else stays on the deployed vessel.
  • Commits are real. The board's 60s idle autosave turns test drags into actual commits on the project's config repo, attributed to you. Revertable via git, but be conscious of it on a production project.
  • Live agents orphan local edits. The board re-reads HEAD every 30s; a HEAD moved by an agent or another member discards your uncommitted working-tree edits (by design — see above).
  • The tunnel exposes vite dev, not project data. Direct hits on the tunnel get only the landing page, and a forged project header without a valid cookie dies at os. What is public is the dev server itself (this checkout's source, HMR endpoints) — fine here, but don't reuse the pattern for a repo with secrets in the checkout.
  • OS_BASE_URL must be https://os.iterate.com (the committed default) — the forwarded production token means nothing to a local os.

Dev-mode through any proxy needs server.allowedHosts (set in vite.config.ts) — without it, vite 403s proxied Hosts and hydration fails in ways that look like framework bugs.

Deployment

pnpm run deploy # vite build && wrangler deploy

No secrets. The only var is OS_BASE_URL (defaults to https://os.iterate.com in wrangler.jsonc).

About

Kanban task board for any iterate project's /repos/config — a standalone TanStack Start app on Cloudflare Workers, mutually authenticated with os.iterate.com

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

iterate tasks

A Kanban board over the tasks/ folder of any iterate project's config repo — deployed as a stateless vessel at tasks.iterate.workers.dev. It never holds project secrets or user sessions of its own. Every useful request arrives through a reverse proxy in the project's /repos/config worker on a host like tasks--<slug>.iterate.app.

Per connection the vessel:

  1. Reads the trusted x-itx-project-id header and the platform's iterate-project-auth cookie (forwarded by the proxy).
  2. Opens a Cap'n Web WebSocket to os.iterate.com/api and authenticates with { type: "project-app-session", token }.
  3. Reads/writes tasks/ markdown in /repos/config via listTaskFiles / commitFiles — the git history of the config repo is the board's history, attributed to the connected user.

The board is a collaborative checkout: loading / creates one and redirects to its shareable /c/<checkout-id> URL. A checkout is a y-partyserverYServer Durable Object (named <projectId>:<checkoutId>) holding an in-DO working copy of the repo's task files as one Yjs doc — a files map of path → Y.Text plus a meta map with the base commit it was seeded from at first join. Everyone on the link edits the same doc over the stock y-protocols WebSocket wire (/api/checkout/<id>): drags, adds, deletes, and each keystroke in the CodeMirror task editor sync live, with presence chips and named, colored remote cursors (y-codemirror.next + awareness). The doc persists as one update blob in DO storage (debounced onSave), so a checkout survives eviction.

Changed cards wear an A/M mark against the base commit, pending deletions stay visible (and reversible) in a strip above the board, and the Commit button — or a 60s idle autosave — POSTs to the DO, which flushes the doc's diff against base as ONE commitFiles batch attributed to the poster: typed message, AI-generated one (/generate-message via ai.run), or a deterministic summary. The new base syncs back through the doc, clearing everyone's marks. External commits to the repo are ignored while a checkout lives — start a fresh checkout to pick up a new HEAD.

There is no pairing form, no OIDC client, and no capability door. The only DO state is the Yjs blob; auth is per-join and per-op (the token is verified by using it against the platform).

Using it

Add a tasks app branch to the project's config worker.ts that gates on project membership and reverse-proxies this vessel:

// tasks app — reverse-proxy the vessel at tasks.iterate.workers.devexportclassTasksAppextendsIterateWorkerEntrypoint{asyncfetch(req: Request): Promise<Response>{
using itx=awaitthis.env.ITX.get();// (a) project-member auth gate — return its response when non-nullconstauth=awaititx.auth.get({policy: "project-member"}).fetch(req);if(auth)returnauth;// (b) transparent proxy: pages, assets, and WebSocket upgrades.// The kv knob points the proxy at a dev tunnel instead of the deployed// vessel (see "Developing against a live project" in the tasks repo's// README); absent knob means production behavior.constdescription=awaititx.__describe();consturl=newURL(req.url);url.protocol="https:";url.host=(awaititx.kv.get("tasks-app-origin"))??"tasks.iterate.workers.dev";constheaders=newHeaders(req.headers);headers.set("x-itx-project-id",description.projectId);returnfetch(newRequest(url,{method: req.method,
headers,body: req.body,redirect: "manual",}));}}

Wire that class into the project's app router the same way as the seeded HelloApp / InternalApp examples. Then open https://tasks--<slug>.iterate.app/ — sign-in is the platform's project- member gate; the board UI is this app at /.

Drag cards, add tasks, click a card to edit its markdown together — everyone on the checkout link sees the same board, each other's chips, and each other's cursors in the editor. Commit from the Commit button (the ▾ panel reviews the change set, writes an AI commit message, or discards everything), or let the 60s idle autosave commit with a generated summary. A checkout is pinned to the base commit it was seeded from; / always starts a fresh one from HEAD.

Hitting tasks.iterate.workers.dev directly serves only a landing page with the same proxy snippet — no project context, no board.

Development

pnpm install
cp .dev.vars.example .dev.vars
pnpm dev

.dev.vars.example points OS_BASE_URL at https://os.iterate.com — the develop-against-production loop below, which is the loop you usually want. Point it at a local os dev server (http://localhost:<port>) to run fully local instead. Either way the vessel does not mint sessions: requests need the x-itx-project-id header and a valid iterate-project-auth cookie stamped on them (a project's proxy does this; headless, use scripts/probe-board-authed.mjs).

Developing against a live project

The vessel is stateless and auth rides with each connection (the proxy stamps the project header and forwards the user's cookie; os verifies the token), so "deployed vessel" vs "your laptop" is just a hostname swap in the project's proxy. Run local dev behind a captun tunnel and flip the project's tasks-app-origin kv knob at it — you get platform login as yourself, real project data, every commit attributed to you, but the app code is your local checkout with HMR. Full guide: the platform's remote-apps doc.

Prerequisite: the project's config worker reads the knob with the deployed host as fallback, as in the proxy snippet above.

The daily loop, two commands:

# 1. in this repo — local vite dev, publicly tunneled (HTTP + WebSocket):
CAPTUN_TUNNEL_NAME=me-tasks \
CAPTUN_TOKEN=$(doppler secrets get CAPTUN_TOKEN --plain --project _shared --config dev) \
pnpm dev
# 2. point the project at your tunnel (from the monorepo's apps/os):
doppler run --config prd -- pnpm cli itx run --context <project-id> \
-e 'await itx.kv.set("tasks-app-origin", "me-tasks.tunnels.iterate.com")'

Then open https://tasks--<slug>.iterate.app in a normal browser. Flip back with itx.kv.delete("tasks-app-origin") — absent knob means the deployed vessel, byte-identical to production.

Know before you dogfood:

  • Prefer the per-user variant (in the guide): the project-wide knob routes every member's traffic — including their session cookies — to your laptop while it is set. Per-user routing sends only your own sessions to the tunnel; everyone else stays on the deployed vessel.
  • Commits are real. The board's 60s idle autosave turns test drags into actual commits on the project's config repo, attributed to you. Revertable via git, but be conscious of it on a production project.
  • Live agents orphan local edits. The board re-reads HEAD every 30s; a HEAD moved by an agent or another member discards your uncommitted working-tree edits (by design — see above).
  • The tunnel exposes vite dev, not project data. Direct hits on the tunnel get only the landing page, and a forged project header without a valid cookie dies at os. What is public is the dev server itself (this checkout's source, HMR endpoints) — fine here, but don't reuse the pattern for a repo with secrets in the checkout.
  • OS_BASE_URL must be https://os.iterate.com (the committed default) — the forwarded production token means nothing to a local os.

Dev-mode through any proxy needs server.allowedHosts (set in vite.config.ts) — without it, vite 403s proxied Hosts and hydration fails in ways that look like framework bugs.

Deployment

pnpm run deploy # vite build && wrangler deploy

No secrets. The only var is OS_BASE_URL (defaults to https://os.iterate.com in wrangler.jsonc).

About

Kanban task board for any iterate project's /repos/config — a standalone TanStack Start app on Cloudflare Workers, mutually authenticated with os.iterate.com

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

iterate tasks

A Kanban board over the tasks/ folder of any iterate project's config repo — deployed as a stateless vessel at tasks.iterate.workers.dev. It never holds project secrets or user sessions of its own. Every useful request arrives through a reverse proxy in the project's /repos/config worker on a host like tasks--<slug>.iterate.app.

Per connection the vessel:

  1. Reads the trusted x-itx-project-id header and the platform's iterate-project-auth cookie (forwarded by the proxy).
  2. Opens a Cap'n Web WebSocket to os.iterate.com/api and authenticates with { type: "project-app-session", token }.
  3. Reads/writes tasks/ markdown in /repos/config via listTaskFiles / commitFiles — the git history of the config repo is the board's history, attributed to the connected user.

The board is a collaborative checkout: loading / creates one and redirects to its shareable /c/<checkout-id> URL. A checkout is a y-partyserverYServer Durable Object (named <projectId>:<checkoutId>) holding an in-DO working copy of the repo's task files as one Yjs doc — a files map of path → Y.Text plus a meta map with the base commit it was seeded from at first join. Everyone on the link edits the same doc over the stock y-protocols WebSocket wire (/api/checkout/<id>): drags, adds, deletes, and each keystroke in the CodeMirror task editor sync live, with presence chips and named, colored remote cursors (y-codemirror.next + awareness). The doc persists as one update blob in DO storage (debounced onSave), so a checkout survives eviction.

Changed cards wear an A/M mark against the base commit, pending deletions stay visible (and reversible) in a strip above the board, and the Commit button — or a 60s idle autosave — POSTs to the DO, which flushes the doc's diff against base as ONE commitFiles batch attributed to the poster: typed message, AI-generated one (/generate-message via ai.run), or a deterministic summary. The new base syncs back through the doc, clearing everyone's marks. External commits to the repo are ignored while a checkout lives — start a fresh checkout to pick up a new HEAD.

There is no pairing form, no OIDC client, and no capability door. The only DO state is the Yjs blob; auth is per-join and per-op (the token is verified by using it against the platform).

Using it

Add a tasks app branch to the project's config worker.ts that gates on project membership and reverse-proxies this vessel:

// tasks app — reverse-proxy the vessel at tasks.iterate.workers.devexportclassTasksAppextendsIterateWorkerEntrypoint{asyncfetch(req: Request): Promise<Response>{
using itx=awaitthis.env.ITX.get();// (a) project-member auth gate — return its response when non-nullconstauth=awaititx.auth.get({policy: "project-member"}).fetch(req);if(auth)returnauth;// (b) transparent proxy: pages, assets, and WebSocket upgrades.// The kv knob points the proxy at a dev tunnel instead of the deployed// vessel (see "Developing against a live project" in the tasks repo's// README); absent knob means production behavior.constdescription=awaititx.__describe();consturl=newURL(req.url);url.protocol="https:";url.host=(awaititx.kv.get("tasks-app-origin"))??"tasks.iterate.workers.dev";constheaders=newHeaders(req.headers);headers.set("x-itx-project-id",description.projectId);returnfetch(newRequest(url,{method: req.method,
headers,body: req.body,redirect: "manual",}));}}

Wire that class into the project's app router the same way as the seeded HelloApp / InternalApp examples. Then open https://tasks--<slug>.iterate.app/ — sign-in is the platform's project- member gate; the board UI is this app at /.

Drag cards, add tasks, click a card to edit its markdown together — everyone on the checkout link sees the same board, each other's chips, and each other's cursors in the editor. Commit from the Commit button (the ▾ panel reviews the change set, writes an AI commit message, or discards everything), or let the 60s idle autosave commit with a generated summary. A checkout is pinned to the base commit it was seeded from; / always starts a fresh one from HEAD.

Hitting tasks.iterate.workers.dev directly serves only a landing page with the same proxy snippet — no project context, no board.

Development

pnpm install
cp .dev.vars.example .dev.vars
pnpm dev

.dev.vars.example points OS_BASE_URL at https://os.iterate.com — the develop-against-production loop below, which is the loop you usually want. Point it at a local os dev server (http://localhost:<port>) to run fully local instead. Either way the vessel does not mint sessions: requests need the x-itx-project-id header and a valid iterate-project-auth cookie stamped on them (a project's proxy does this; headless, use scripts/probe-board-authed.mjs).

Developing against a live project

The vessel is stateless and auth rides with each connection (the proxy stamps the project header and forwards the user's cookie; os verifies the token), so "deployed vessel" vs "your laptop" is just a hostname swap in the project's proxy. Run local dev behind a captun tunnel and flip the project's tasks-app-origin kv knob at it — you get platform login as yourself, real project data, every commit attributed to you, but the app code is your local checkout with HMR. Full guide: the platform's remote-apps doc.

Prerequisite: the project's config worker reads the knob with the deployed host as fallback, as in the proxy snippet above.

The daily loop, two commands:

# 1. in this repo — local vite dev, publicly tunneled (HTTP + WebSocket):
CAPTUN_TUNNEL_NAME=me-tasks \
CAPTUN_TOKEN=$(doppler secrets get CAPTUN_TOKEN --plain --project _shared --config dev) \
pnpm dev
# 2. point the project at your tunnel (from the monorepo's apps/os):
doppler run --config prd -- pnpm cli itx run --context <project-id> \
-e 'await itx.kv.set("tasks-app-origin", "me-tasks.tunnels.iterate.com")'

Then open https://tasks--<slug>.iterate.app in a normal browser. Flip back with itx.kv.delete("tasks-app-origin") — absent knob means the deployed vessel, byte-identical to production.

Know before you dogfood:

  • Prefer the per-user variant (in the guide): the project-wide knob routes every member's traffic — including their session cookies — to your laptop while it is set. Per-user routing sends only your own sessions to the tunnel; everyone else stays on the deployed vessel.
  • Commits are real. The board's 60s idle autosave turns test drags into actual commits on the project's config repo, attributed to you. Revertable via git, but be conscious of it on a production project.
  • Live agents orphan local edits. The board re-reads HEAD every 30s; a HEAD moved by an agent or another member discards your uncommitted working-tree edits (by design — see above).
  • The tunnel exposes vite dev, not project data. Direct hits on the tunnel get only the landing page, and a forged project header without a valid cookie dies at os. What is public is the dev server itself (this checkout's source, HMR endpoints) — fine here, but don't reuse the pattern for a repo with secrets in the checkout.
  • OS_BASE_URL must be https://os.iterate.com (the committed default) — the forwarded production token means nothing to a local os.

Dev-mode through any proxy needs server.allowedHosts (set in vite.config.ts) — without it, vite 403s proxied Hosts and hydration fails in ways that look like framework bugs.

Deployment

pnpm run deploy # vite build && wrangler deploy

No secrets. The only var is OS_BASE_URL (defaults to https://os.iterate.com in wrangler.jsonc).

About

Kanban task board for any iterate project's /repos/config — a standalone TanStack Start app on Cloudflare Workers, mutually authenticated with os.iterate.com

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

iterate tasks

A Kanban board over the tasks/ folder of any iterate project's config repo — deployed as a stateless vessel at tasks.iterate.workers.dev. It never holds project secrets or user sessions of its own. Every useful request arrives through a reverse proxy in the project's /repos/config worker on a host like tasks--<slug>.iterate.app.

Per connection the vessel:

  1. Reads the trusted x-itx-project-id header and the platform's iterate-project-auth cookie (forwarded by the proxy).
  2. Opens a Cap'n Web WebSocket to os.iterate.com/api and authenticates with { type: "project-app-session", token }.
  3. Reads/writes tasks/ markdown in /repos/config via listTaskFiles / commitFiles — the git history of the config repo is the board's history, attributed to the connected user.

The board is a collaborative checkout: loading / creates one and redirects to its shareable /c/<checkout-id> URL. A checkout is a y-partyserverYServer Durable Object (named <projectId>:<checkoutId>) holding an in-DO working copy of the repo's task files as one Yjs doc — a files map of path → Y.Text plus a meta map with the base commit it was seeded from at first join. Everyone on the link edits the same doc over the stock y-protocols WebSocket wire (/api/checkout/<id>): drags, adds, deletes, and each keystroke in the CodeMirror task editor sync live, with presence chips and named, colored remote cursors (y-codemirror.next + awareness). The doc persists as one update blob in DO storage (debounced onSave), so a checkout survives eviction.

Changed cards wear an A/M mark against the base commit, pending deletions stay visible (and reversible) in a strip above the board, and the Commit button — or a 60s idle autosave — POSTs to the DO, which flushes the doc's diff against base as ONE commitFiles batch attributed to the poster: typed message, AI-generated one (/generate-message via ai.run), or a deterministic summary. The new base syncs back through the doc, clearing everyone's marks. External commits to the repo are ignored while a checkout lives — start a fresh checkout to pick up a new HEAD.

There is no pairing form, no OIDC client, and no capability door. The only DO state is the Yjs blob; auth is per-join and per-op (the token is verified by using it against the platform).

Using it

Add a tasks app branch to the project's config worker.ts that gates on project membership and reverse-proxies this vessel:

// tasks app — reverse-proxy the vessel at tasks.iterate.workers.devexportclassTasksAppextendsIterateWorkerEntrypoint{asyncfetch(req: Request): Promise<Response>{
using itx=awaitthis.env.ITX.get();// (a) project-member auth gate — return its response when non-nullconstauth=awaititx.auth.get({policy: "project-member"}).fetch(req);if(auth)returnauth;// (b) transparent proxy: pages, assets, and WebSocket upgrades.// The kv knob points the proxy at a dev tunnel instead of the deployed// vessel (see "Developing against a live project" in the tasks repo's// README); absent knob means production behavior.constdescription=awaititx.__describe();consturl=newURL(req.url);url.protocol="https:";url.host=(awaititx.kv.get("tasks-app-origin"))??"tasks.iterate.workers.dev";constheaders=newHeaders(req.headers);headers.set("x-itx-project-id",description.projectId);returnfetch(newRequest(url,{method: req.method,
headers,body: req.body,redirect: "manual",}));}}

Wire that class into the project's app router the same way as the seeded HelloApp / InternalApp examples. Then open https://tasks--<slug>.iterate.app/ — sign-in is the platform's project- member gate; the board UI is this app at /.

Drag cards, add tasks, click a card to edit its markdown together — everyone on the checkout link sees the same board, each other's chips, and each other's cursors in the editor. Commit from the Commit button (the ▾ panel reviews the change set, writes an AI commit message, or discards everything), or let the 60s idle autosave commit with a generated summary. A checkout is pinned to the base commit it was seeded from; / always starts a fresh one from HEAD.

Hitting tasks.iterate.workers.dev directly serves only a landing page with the same proxy snippet — no project context, no board.

Development

pnpm install
cp .dev.vars.example .dev.vars
pnpm dev

.dev.vars.example points OS_BASE_URL at https://os.iterate.com — the develop-against-production loop below, which is the loop you usually want. Point it at a local os dev server (http://localhost:<port>) to run fully local instead. Either way the vessel does not mint sessions: requests need the x-itx-project-id header and a valid iterate-project-auth cookie stamped on them (a project's proxy does this; headless, use scripts/probe-board-authed.mjs).

Developing against a live project

The vessel is stateless and auth rides with each connection (the proxy stamps the project header and forwards the user's cookie; os verifies the token), so "deployed vessel" vs "your laptop" is just a hostname swap in the project's proxy. Run local dev behind a captun tunnel and flip the project's tasks-app-origin kv knob at it — you get platform login as yourself, real project data, every commit attributed to you, but the app code is your local checkout with HMR. Full guide: the platform's remote-apps doc.

Prerequisite: the project's config worker reads the knob with the deployed host as fallback, as in the proxy snippet above.

The daily loop, two commands:

# 1. in this repo — local vite dev, publicly tunneled (HTTP + WebSocket):
CAPTUN_TUNNEL_NAME=me-tasks \
CAPTUN_TOKEN=$(doppler secrets get CAPTUN_TOKEN --plain --project _shared --config dev) \
pnpm dev
# 2. point the project at your tunnel (from the monorepo's apps/os):
doppler run --config prd -- pnpm cli itx run --context <project-id> \
-e 'await itx.kv.set("tasks-app-origin", "me-tasks.tunnels.iterate.com")'

Then open https://tasks--<slug>.iterate.app in a normal browser. Flip back with itx.kv.delete("tasks-app-origin") — absent knob means the deployed vessel, byte-identical to production.

Know before you dogfood:

  • Prefer the per-user variant (in the guide): the project-wide knob routes every member's traffic — including their session cookies — to your laptop while it is set. Per-user routing sends only your own sessions to the tunnel; everyone else stays on the deployed vessel.
  • Commits are real. The board's 60s idle autosave turns test drags into actual commits on the project's config repo, attributed to you. Revertable via git, but be conscious of it on a production project.
  • Live agents orphan local edits. The board re-reads HEAD every 30s; a HEAD moved by an agent or another member discards your uncommitted working-tree edits (by design — see above).
  • The tunnel exposes vite dev, not project data. Direct hits on the tunnel get only the landing page, and a forged project header without a valid cookie dies at os. What is public is the dev server itself (this checkout's source, HMR endpoints) — fine here, but don't reuse the pattern for a repo with secrets in the checkout.
  • OS_BASE_URL must be https://os.iterate.com (the committed default) — the forwarded production token means nothing to a local os.

Dev-mode through any proxy needs server.allowedHosts (set in vite.config.ts) — without it, vite 403s proxied Hosts and hydration fails in ways that look like framework bugs.

Deployment

pnpm run deploy # vite build && wrangler deploy

No secrets. The only var is OS_BASE_URL (defaults to https://os.iterate.com in wrangler.jsonc).

About

Kanban task board for any iterate project's /repos/config — a standalone TanStack Start app on Cloudflare Workers, mutually authenticated with os.iterate.com

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages