Brod

Use case

A warehouse inventory tracker people use from their phones

What is in stock, where it is, and who moved it last. Warehouse staff update counts from a phone between shelves, sales checks stock from a laptop, and every change carries a name. Your agent builds the app; Brod runs it behind sign-in.

What the tool does

The history is the point. A shared spreadsheet tells you the count is wrong. It does not tell you who changed it at 6pm on Friday.

  • Warehouse staff

    Pick a location, find an item, and add or remove stock with one tap.

  • Sales and support

    Look up what is available, but cannot change counts.

  • Everyone

    Can see the history of an item: each move, who made it and when.

The parts of Brod it uses

NeedHow it works on Brod
Sign-in on a phonePeople sign in to the workspace with an emailed link. The link opens a page with a Continue button, and a session lasts 30 days, so staff are not signing in every shift.
Who may change countsBrod's default app roles are owner, editor and viewer. Grant the warehouse group editor and everyone else viewer. The app reads x-brod-role and refuses writes from viewers.
Stock and historyThe app's SQLite database, env.DB. batch runs several statements in one transaction.
Small per-person stateThe key-value store, env.KV, for things like each person's last location.
Safe changesEvery deploy is a numbered version. Updates go to staging first. Rollback serves an earlier version from the next request.

The prompt

"Mobile-first" is an instruction to your agent, not a Brod feature. Brod serves whatever HTML, CSS and JavaScript the agent builds.

Ask for large tap targets and a layout that works one-handed, then check it on a real phone before you share it.

Build a mobile-first inventory tracker and deploy it to Brod as stock. Items have a SKU, a name, a location and a quantity. Editors add or remove stock with big +/- buttons; viewers can only look. Record every move with the person's email in env.DB, in the same transaction as the count change. Remember each person's last location in env.KV. Give the warehouse group the editor role and the workspace viewer.

get_workspace · deploy_app · set_access

Deployed: https://stock.acme.brod.sh (version 1). Opening it to the workspace and the warehouse group needs your confirmation: open this link.

The server code

The handler that changes a count, from _worker.js.

// _worker.js
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const userId = request.headers.get('x-brod-user-id');
    const email = request.headers.get('x-brod-user-email');
    const role = request.headers.get('x-brod-role');

    // Change a count: editors and the owner only
    if (request.method === 'POST' && url.pathname === '/api/move') {
      if (role !== 'editor' && role !== 'owner') return new Response('Read-only', { status: 403 });
      const { sku, location, delta } = await request.json();
      if (!Number.isInteger(delta) || delta === 0) return new Response('Bad amount', { status: 400 });

      // The count and the history row change together, or not at all
      await env.DB.batch([
        { sql: 'UPDATE items SET qty = qty + ? WHERE sku = ? AND location = ?', params: [delta, sku, location] },
        { sql: 'INSERT INTO moves (sku, location, delta, by, at) VALUES (?, ?, ?, ?, ?)',
          params: [sku, location, delta, email, Date.now()] },
      ]);
      await env.KV.put('last-location:' + userId, location);
      return Response.json({ ok: true });
    }

    // Where this person was working last time
    if (url.pathname === '/api/me') {
      return Response.json({ role, lastLocation: await env.KV.get('last-location:' + userId) });
    }

    // History of one item
    if (url.pathname === '/api/moves') {
      const rows = await env.DB.query(
        'SELECT * FROM moves WHERE sku = ? ORDER BY at DESC LIMIT 50', [url.searchParams.get('sku')]);
      return Response.json({ moves: rows });
    }
    return new Response('Not found', { status: 404 });
  },
};
  • Role on every request. Brod tells the app the role on every request, and strips any x-brod-* header a visitor tries to send.
  • Checked on the server. Enforcing "viewers cannot change counts" is the app's job, which is why the check sits on the server rather than only hiding the buttons.

Changing it without losing a day's counts

The warehouse will ask for barcode fields, a low-stock list, a CSV export. Each request is a new version.

  1. Staging first

    When an agent updates the live app, the new version goes to a staging address; the live app keeps running until someone publishes. Only the owner, admins, builders and people with the top role can open staging.

  2. Roll back if it breaks

    If a published version breaks the floor's workflow, ask the agent to rollback. The earlier version serves from the next request and nothing is rebuilt.

  3. Keep schema changes additive

    Rollback moves the code, not the data: moves recorded in the meantime stay recorded. Ask your agent to make schema changes additive (new columns and tables, not renames), so an older version still runs against the newer database.

Staging uses the live data

Staging and every version share one database, key-value store and file storage. A count you change while testing on staging changes the real count. Test with a dummy SKU, or on a quiet afternoon.

If two people change the app with their agents, each deploy carries the version it started from. A deploy based on an old version is refused, not merged, so one person's change does not silently undo another's.

Questions

Can temporary staff use it without a full account?

A builder can invite them as a guest to this one app. Guests see only the apps they were given. Invitations from an agent wait for a person to confirm.

Can I stop the app during a stocktake?

Yes. Pause it with set_paused, and visitors see that the owner has paused it until you resume.

Does it work offline?

No. Each change is a request to the app's server code, so a phone needs a connection when it saves a move.

Can it send a low-stock email every morning?

Not yet. Brod apps cannot send email or run scheduled jobs today. A low-stock page that people open is the way to do it for now.

Your AI already built the app. Put it in your team's hands.