{"slug": "creatordesk-a-self-hosted-approval-loop-for-commission-artists", "title": "CreatorDesk: a self-hosted approval loop for commission artists", "summary": "A developer built CreatorDesk, a self-hosted, MIT-licensed open-source tool that tracks which version of a commission a client approved and why. Artists upload work-in-progress images, send clients a private tokenized link, and clients approve or request changes without creating an account, with every revision and decision timestamped. The Next.js 14 and SQLite app ships as v0.1.1 with 32 passing tests and runs locally after four commands.", "body_md": "*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle](https://dev.to/challenges/hacktoberfest-week2-2026-10-12)*\n\nCreatorDesk is a self-hosted, open-source tool that solves one specific problem for independent digital artists: **tracking which version of a commission a client approved, and why.**\n\nAn artist uploads a work-in-progress (a sketch, a lineart, a colored render), sends the client a private link, and the client clicks **Approve** or **Request changes** with optional written feedback. Every revision and every decision is recorded with a timestamp. The client never creates an account. The artist never loses the conversation to a DM thread.\n\nWho would pay for it: a working commission artist who does 4–15 paid commissions a month, whose income depends on delivering the right version, and who currently tracks client feedback in Discord DMs, Trello boards, and a spreadsheet. CreatorDesk replaces that with one page the client can actually use and one history the artist can actually point to when scope creep happens.\n\nThe problem it solves is narrow on purpose: **the approval loop, not the whole business.** Not a marketplace, not a portfolio host, not a CRM, not payments. The wedge is the moment between \"here is the sketch\" and \"yes, that's right\" — because that's where commission failures happen, and where artists have the least infrastructure.\n\nThe app runs on `localhost:3000` after four commands:\n\n```\ngit clone https://github.com/johnny603/CreatorDesk\ncd CreatorDesk && npm install\nnpm run db:reset     # applies migrations, seeds a demo artist\nnpm run dev\n```\n\nA full loop — create commission → upload sketch → send link → client requests change → upload v2 → client approves → mark delivered — takes about two minutes and is documented step-by-step in the README.\n\n**Screenshot: artist dashboard, mid-demo.**\n\n```\nCreatorDesk\n+ New commission\n\nTitle              Client   Status                      Updated\n● Test commission  Alex     needs your next revision    2026-10-18T01:26:48Z\n  Test 2           Joe      waiting on client           2026-10-18T01:25:40Z\n  Test 3           Sam      approved — ready to deliver 2026-10-18T01:20:49Z\n```\n\nThe dot (●) marks commissions where the artist has a next action — `in_progress` (client requested changes) and `approved` (client signed off, ready to deliver). The dashboard sorts by *actionability*, not recency, so the answer to \"what do I do next?\" is the top row.\n\n**Screenshot: client approval page.**\n\n```\nTest commission\nVersion 2\n[image of the sketch]\n\n\"hair updated — changed the shape per your note\"\n\nFeedback (optional): [textarea]\n\n[Approve]   [Request changes]\n\n▸ Revision history (2 versions)\n```\n\nThe `<details>` expansion shows v1 (\"you asked for changes: 'make the hair blue' (2026-10-18T01:20:33Z)\") and v2 (\"currently reviewing\"). The client can see what they asked for. The artist can point to it later.\n\nGitHub: [github.com/johnny603/CreatorDesk](https://github.com/johnny603/CreatorDesk) — tagged `v0.1.1`, MIT licensed, 32 passing tests.\n\nThe stack is deliberately boring:\n\n| Layer | Technology | Purpose | \n|---|---|---|\n| App framework | Next.js 14 (App Router) + TypeScript | Server-rendered routes; server actions for mutations | \n| UI | React, minimal client JS | Forms work without hydration where possible | \n| Database | SQLite via `better-sqlite3` | Single-file state; the whole app is `data/` | \n| Data access | Raw SQL in a small module | No ORM; every query is visible | \n| Uploads | Local filesystem, sha256-named | Duplicate uploads are free; no cloud storage | \n| Client auth | Random 256-bit token, sha256-stored | The token in the URL is the entire client session | \n| Artist session | Single-user stub ( `session.ts` ) | Real auth is deferred until validation | \n\n**Where the open pieces matter — and where they are the reason the project works:**\n\nThe case for open-source here is not \"we used an LLM.\" It is the same case the challenge asks about: **does the open approach let you do something a closed one can't?**\n\nThree specific answers, all structural:\n\n**Data control.** A commission artist's work-in-progress is often under NDA, embargo, or just the artist's own embargo until the client has paid. Sending those images through a third-party SaaS means the artist is trusting a company's retention policy with their client's intellectual property. CreatorDesk is self-hosted: `data/creatordesk.sqlite` and `data/uploads/` are on the artist's own machine, and the README documents exactly how to back them up. There is no server the artist doesn't control.\n\n**Cost.** A self-hosted SQLite app costs $0/mo to run on a $5 VPS or an old Mac Mini. HoneyBook is ~$29/mo; Dubsado is more. For a part-time artist doing 3 commissions a month, the closed tools' pricing is the single most cited reason for not adopting them. The open tool's cost is the price of the machine it runs on, which the artist already owns.\n\n**Portability.** The schema is a plain `.sql` file. If CreatorDesk is abandoned, the artist can `sqlite3 data/creatordesk.sqlite` and export every commission, every revision's file path, every decision, every feedback note. The data is not trapped behind an API the vendor can deprecate.\n\n**The AI piece — what it actually is, and what it is not:**\n\nThis is not an \"AI features\" project. CreatorDesk does not call an LLM at runtime, and the artist is not writing prompts. The role of open AI tooling is in the **build**, not the runtime: the project was planned, reviewed, and debugged in **Claude Code** over a multi-day session, with open-source design patterns (Facade applied *lightly*, State as an explicit transition table, everything else deferred) as the reference. The repository's commit history and this blog post's companion agent session are the record of that.\n\nThat distinction matters for the challenge's framing. The challenge asks for a project where open pieces are what makes it work — not for every project to ship a model. Here, the open-source *ethos* (self-host, portability, no vendor lock-in) is what makes the product's value proposition possible in the first place. A closed-API version of this tool would defeat its own pitch.\n\n**The system diagram:**\n\n```\n┌──────────────────────┐         ┌────────────────────────┐\n│ Client (browser)     │         │ Artist (browser)       │\n│ no account           │         │ single-user session    │\n└──────────┬───────────┘         └───────────┬────────────┘\n           │ token in URL                    │ cookie\n           ▼                                 ▼\n┌──────────────────────────────────────────────────────────┐\n│ Next.js routes (src/app)                                 │\n│  /a/[token]              client approval page            │\n│  /a/[token]/decision     POST approve / request changes  │\n│  /c/[commissionId]       artist detail + upload          │\n│  /c/[commissionId]/actions server actions                │\n│  /api/commissions        create commission               │\n│  /api/files/[filename]   file serving, token-scoped      │\n└──────────┬───────────────────────────────────────────────┘\n           │\n           ▼\n┌──────────────────────────────────────────────────────────┐\n│ lib (src/lib)                                            │\n│  commissions.ts    workflow rules + token lookup         │\n│  revisions.ts      the atomic decision claim             │\n│  approval-tokens.ts  security boundary                   │\n│  uploads.ts        sha256-named file storage             │\n│  db.ts             connection + transaction wrapper      │\n└──────────┬───────────────────────────────────────────────┘\n           │\n           ▼\n┌──────────────────────────────────────────────────────────┐\n│ data/                                                    │\n│  creatordesk.sqlite (artists, commissions, revisions)    │\n│  uploads/<sha256>.<ext>                                  │\n└──────────────────────────────────────────────────────────┘\n```\n\n**The workflow, start to finish — the client approval loop:**\n\n`/c/new`) → POSTs to `/api/commissions`. `commissions.create()` generates a 256-bit random token, stores `sha256(token)` and a 30-day expiry, and returns the raw token once. The artist sees a \"Commission created\" page with a copy button.`/c/[commissionId]`. `revisions.submitRevision()` inserts a row with `version_number = 1, decision = 'pending'`, and updates the commission to `awaiting_approval`, in a single transaction.`/a/[token]`.` approval-tokens.hashToken(token)` + `commissions.findByTokenHash(hash)` resolves the token to exactly one commission (or returns `undefined` if expired). If the latest revision is `pending`, the page renders the image, the artist's note, two buttons, and a collapsed revision history. If not, it renders a status-specific message (\"The artist is preparing your first revision\" / \"You approved the latest version\" / etc.).`/a/[token]/decision`. `revisions.recordDecision(revisionId, decision, feedback)` runs a single SQL transaction:\n`UPDATE revisions SET decision = ? WHERE id = ? AND decision = 'pending' AND version_number = (SELECT MAX(version_number) ...) AND EXISTS (SELECT 1 FROM commissions WHERE ... status = 'awaiting_approval')`\n`already_decided` / `not_latest` / `wrong_state` / `not_found`) and returns it to the route, which returns a specific message.`approved` or `in_progress`, inside the same transaction.`/a/[token]?thanks=1`). The artist's dashboard now shows the decision.` revisions` table forever. The client page shows it collapsed; the artist page shows it fully.\nThe transaction at step 5 is the load-bearing part. All four race scenarios the project tests (`already_decided`, `not_latest`, `wrong_state`, `not_found`) are about what happens when two of those steps overlap — a stale tab, a duplicate click, an artist uploading while the client is deciding. The SQL is one statement, and its `WHERE` clause is the entire concurrency control.\n\nThe three reasons from above, restated because the challenge asks directly:\n\n**Client work is confidential by default.** A sketch under commission is not public. An artist's client may be a small business commissioning packaging art, an author commissioning a book cover, a family commissioning a portrait. None of them expect their WIP to leave the artist's machine. A self-hosted tool makes that a property, not a promise. A closed-API version of this app would need to explain its retention policy in every client email, which is a conversation neither party wants.\n\n**A working artist's tooling budget is near zero.** The $29/mo tools are not priced wrong — they're priced for the agency freelancers who are their market. A part-time illustrator doing 3 commissions a month cannot amortize a $29/mo subscription over a $150 commission. The self-hosted version costs the artist nothing they aren't already paying for. That is not a marketing angle; it is the difference between the tool existing and not existing for that customer.\n\n**The schema is the product.** `migrations/001_init.sql` is 58 lines. Anyone can read it in a minute and understand every state a commission can be in. That's the open-source contract: if I disappear, the data is yours, and the shape of it is legible. A closed SaaS's schema is invisible; a self-hosted OSS schema is a `.sql` file in a git repo. For a tool holding a person's client history, legibility is a feature.\n\nThe build was done in **Claude Code** across several sessions. The agent session includes: product direction research, the four design review rounds on the transaction contract, the two test-suite bug fixes (`classifyFailure` ordering and the `submitRevision` guard), the two `next build` type errors (`BodyInit` on Buffer, the `DecisionResult` conditional-type collapse), and the UX round that added the thanks page and contextual \"nothing to review\" messages.\n\nThe pattern worth naming from that session: every design-level problem was caught in review, and every *implementation* bug was caught by `npm test` or `npm run build`, not review. That's a useful distinction for anyone considering this workflow — review catches design, tests catch code, and neither substitutes for the other.\n\n(Agent session embed/link per challenge instructions.)\n\nThe honest state: I tested the full loop myself end-to-end on `v0.1.1` — create commission → upload → approve → request changes → upload v2 → approve → mark delivered — with both single and bulk delete verified. That's not a real first user; it's the demo working.\n\nWhat I did **not** do, and what the project's own research plan gates: five interviews with working commission artists before building more. The README's \"What's next\" section says this explicitly:\n\nThe next milestone is **user validation**, not more features. Five interviews with working commission artists, then the answer to: \"is the approval loop actually painful enough that artists will switch for it?\"\n\nThe first real user will come from that round. The tool is ready for them: `git clone`, four commands, a two-minute demo loop, a README that documents what it does and does not do. The scope is deliberately bounded so that the first user's feedback goes to the right place — the next feature, not a bug report about the demo.\n\nRead src/lib/revisions.ts and tell me whether the transaction claim reads correctly to you. If you have written a conditional UPDATE … WHERE … before, you will spot a problem faster than I will.\n\nRun the demo on your machine and tell me where you got stuck. The README assumes Node 20, better-sqlite3 prebuilds, and a browser. Every deviation from that assumption is a bug report I can't write myself.\n\nIf you are a commission artist: the 15-minute interview script is in the README. If you are not, a referral to one is as useful as a review.\n\nThe tool works. What I need next is not more code — it is the data, the review, and the restoration test that tell me whether the thing I built is solving the problem I think it is. That's the honest state of the project, and it's what the next milestone is for.\n\nOpen Source (primary). This is a self-hosted, MIT-licensed project where the open-source property — data ownership, legibility, cost — is the product's value proposition, not an incidental choice. No partner category claimed.", "url": "https://wpnews.pro/news/creatordesk-a-self-hosted-approval-loop-for-commission-artists", "canonical_source": "https://dev.to/jds64/creatordesk-a-self-hosted-approval-loop-for-commission-artists-38jp", "published_at": "2026-10-18 02:18:55+00:00", "updated_at": "2026-10-18 02:40:10.391846+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools"], "entities": ["CreatorDesk", "Next.js", "SQLite", "better-sqlite3", "GitHub", "Hacktoberfest"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/creatordesk-a-self-hosted-approval-loop-for-commission-artists", "markdown": "https://wpnews.pro/news/creatordesk-a-self-hosted-approval-loop-for-commission-artists.md", "text": "https://wpnews.pro/news/creatordesk-a-self-hosted-approval-loop-for-commission-artists.txt", "jsonld": "https://wpnews.pro/news/creatordesk-a-self-hosted-approval-loop-for-commission-artists.jsonld"}}