Apps templates

Full design · Apps

Project tracker

A fast, keyboard first project tracker for small agencies and product teams.

Live preview. Click around, it works.

About this template

A fast, keyboard first project tracker for small agencies and product teams. An inbox of mentions, assignments and due dates; your own work in one list; projects with progress and health; and every project in three layouts over the same issues: a board you drag across statuses, a grouped list you sort and edit in place, and a timeline you drag to move and resize dates with dependencies drawn between bars. Issues carry markdown descriptions, sub tasks, labels, estimates, comments and a full activity log. Cycles show a burn up chart with scope changes, a command palette on Ctrl or Cmd K does the rest, and settings edit the workflow, labels and member roles. Admin, Member and Guest (client) views, where guests see only the projects shared with them and never internal comments.

What the agents are asked

The first message sent when you use this template. You can edit the plan before anything is built.

Adapt this project tracker to my team. Keep the screens (Inbox, My work, Projects, the project view with Board, List and Timeline over the same issues, Issue detail, Cycles with the burn up chart, Search with the command palette, Settings), the keyboard shortcuts and the ? overlay, drag and drop with its keyboard alternative, and the Admin, Member and Guest (client) roles with guests limited to shared projects and blind to internal comments. Change the brand, the workflow statuses, the labels, the members, the project key prefixes and the sample projects and issues so they look like my kind of work. Keep the sample data clearly marked and removable in one step. Where I want real accounts or a shared database for the team, wire in sign in and the tables in schema.sql as the README describes, keeping row level security on every table and every key on the server.

A fast, keyboard first project tracker for small agencies and product teams. An inbox of mentions, assignments and due dates; your own work in one list; projects with progress and health; and every project in three layouts over the same issues: a board you drag across statuses, a grouped list you sort and edit in place, and a timeline (a Gantt drawn in SVG) you drag to move and resize dates, with dependencies drawn between the bars. Issues carry markdown descriptions, sub tasks, labels, estimates, comments and an activity log. Cycles show a burn up chart with every scope change. A command palette on Ctrl or Cmd K does the rest. Three roles: Admin, Member and Guest (client).

"Chalkline" and "Tallow & Finch", the Fremantle design and product studio in the sample, are fictional, as are its clients (Gull Island Ferries, Marri Cellars, Saltbush Physio, Bellbird Bakery), every person and every email address (.example domains). The template is benchmarked against the feel of modern issue trackers (speed, density, keyboard first) and copies none of their look, names or copy. It is not modelled on any real product.

  • Plain HTML, CSS and one ES module. No dependencies, no build step.
  • Light ("Drafting") and dark ("Night shift") themes follow the device; the moon or sun button in the sidebar, the palette, or Settings > Preferences picks one and remembers it. Every colour is a token at the top of styles.css.
  • Phone first: under 900px a bottom tab bar, a floating New issue button and a "Viewing as" select replace the sidebar; under 700px the list becomes stacked rows and pickers open as bottom sheets. The board and timeline scroll sideways.

Art direction

A tool, not a brochure: the product is the visual, and photography is used where people are (avatars) and where the screen would otherwise be empty. The idea behind the brand is a builder's chalk line, the string you snap between two pins to mark a line that is true.

  • Palette. Three solid colours: carbon ink #101418, drafting paper #eef1f2 (cool, not cream) and chalk blue #1d7bd0. Chalk blue is the line colour: progress, the today line, focus rings, the active rail, the burn up "completed" line. As a button fill it deepens to #1766af so white text passes contrast. Health keeps its own green, amber and red; labels get eight muted dots. No gradients on surfaces, no glow, no violet.
  • Type. Bricolage Grotesque (display) at optical size and a slightly condensed width for page titles, project names, the cycle number and big figures; Onest for the interface at 14px with a 1.25 scale; Spline Sans Mono for issue keys and dates in tables. Tight tracking on large headings, 1.6 line height in descriptions.
  • Signature details. Progress is drawn as a chalk line: a dotted string with a solid run and a small bob at its end. Status glyphs are squares (dashed for backlog, filling left to right as work starts, solid with a tick when done); priority reads like the rungs of a level.
  • Photography. Four images from media.json, one shoot: soft overcast light from a sawtooth roof skylight in a converted Fremantle wool store, whitewashed brick, cool 5600K, 35mm for the wide and 85mm for portraits, fine grain. The planning wall (three people at a wall of sticky notes strung along blue lines) is the sign in screen and every empty state. The three portraits are the avatars of the Admin, Member and Guest people everywhere: sidebar, cards, lists, comments, the timeline, settings. Everyone else gets initials on a soft tone. No video: an app does not need a hero loop.
  • Layout. A dense, quiet shell (hairline borders, 4 to 14px radii, square cornered photographs) with one asymmetric move per screen: the most troubled project spans two columns on Projects with its latest update set large beside it; the issue page keeps a sticky properties rail; Cycles leads with one oversized cycle number; sign in splits photograph and form.
  • Motion. One job each, all off under prefers-reduced-motion or Settings > Preferences > Motion:
  • first load: the sidebar and first screen settle in a short stagger, once;
  • screen changes: document.startViewTransition cross fade, feature detected, skipped when the tab is hidden;
  • scroll: project cards rise with animation-timeline: view() inside @supports, with an IntersectionObserver fallback at a 0.2 threshold;
  • photographs: a clip-path: inset() reveal with a slight settle, once;
  • hovers 160ms, eased, no bounce;
  • the orchestrated moment, "the chalk line": switching Board, List and Timeline (buttons, 1 2 3 or the palette), every issue travels from its old place to its new one (a view transition with a name per issue), then the blue today line is pulled aside and released, settling straight like a struck chalk line with a puff of chalk dust. The snap is eased out with no overshoot, and a timer puts the line straight if frames stop.

Photo list

Generated with node tools/generate-imagery.mjs --template project-tracker from media.json and served from /template-media/project-tracker/<name>.webp. Project creation makes these paths absolute.

NameSizeUsed for
planning-wall1600 x 1211Sign in, every empty state (projects, inbox, my work, timeline, cycles), og:image
portrait-tahlia600 x 600Tahlia Rennie, studio director (the Admin)
portrait-sunil600 x 600Sunil Perera, developer (the Member)
portrait-grace600 x 600Grace Halloran, Gull Island Ferries (the Guest)

Roles and demo mode

The top bar reads Viewing as: Admin / Member / Guest (client). It exists for the preview only; the real app shows whoever signed in. The sign in screen (#/signin, or Sign out in the account menu) takes any email and offers the three sample people. It says plainly that nothing is checked.

Admin (Tahlia)Member (Sunil)Guest, client (Grace)
ProjectsAll; create, edit, share, complete, deleteAll; create, edit, completeOnly projects shared with them (Grace sees GIF)
IssuesEverythingEverythingRead; file requests (land in Backlog, labelled Client request); no drag, no edits
CommentsPublic and internal notesPublic and internal notesPublic only; internal notes are never shown
CyclesYesYesHidden ("Not shared with you")
SettingsWorkflow, labels, members, preferences, dataRead only, plus own preferencesOwn preferences

These rules live in the can object in script.js and mirror the row level security in schema.sql. Notifications respect them too: a guest is never notified about an issue they cannot see, or about an internal note.

Everything is kept in localStorage (chalkline:data:v1 for the workspace, chalkline:prefs:v1 for personal preferences). Sample rows carry is_sample, the sidebar shows a Sample badge, and Settings > Data > Remove sample data empties the workspace in one step (keeping the workflow, labels and the admin), with Undo. "Reset demo data" puts it all back.

Screens

  • Inbox (#/inbox): mentions, assignments, status changes, comments and client requests, plus computed "Due soon" (Settings sets 1, 3 or 7 days). Tabs, unread dots, a preview pane with quick status and assignee changes, archive with Undo, mark all read. J and K move, E archives.
  • My work (#/my): a week strip (Monday or Sunday start, from Settings) with overdue work on the left, then your issues grouped by status. "Add due dates to calendar" writes an .ics file. Guests see "My requests".
  • Projects (#/projects): cards with the chalk line progress, health (On track, At risk, Off track) you change with an update note, target date and team. Filter active or completed, sort by attention, target, progress or name.
  • Project (#/project/<id>/board|list|timeline): header with health, lead, target, progress and the latest update; filters (text, mine, assignee, label, priority) shared by all three layouts.
  • Board: drag cards between statuses and within a column with pointer events (mouse, pen, and touch from the grip). Keyboard: focus a card, Space picks it up, arrows move it, Space drops, Escape cancels; every move is announced.
  • List: grouped by status, assignee, priority or nothing; sortable columns with aria-sort; inline edits for priority, status, title (R or the pencil), assignee, estimate and due date; checkboxes (or X) for bulk status, assign, priority and delete with Undo; Export CSV and Print (a print stylesheet leaves just the list).
  • Timeline: an SVG Gantt with weeks, weekends, month labels, the project target and the today line. Drag a bar to move it, drag its ends to resize; dependencies are drawn as elbow connectors, dashed red when the blocked issue starts before its blocker is due. Focused bars move with the arrow keys (Shift for the due date, Alt for the start). Days or months zoom, a Today button, and a tray for unscheduled issues.
  • Issue (#/issue/GIF-3): rename in place; markdown description (headings, bold, italic, code, links, lists, quotes, and task lists you tick in place); sub tasks with their own status; blocked by and blocks links (loops are refused); comments with @mentions (a suggestion list; guests are only offered on public comments) and an Internal note switch that says who will see the comment; activity log; a properties rail (S, P, A, L, E, D shortcuts); copy link, duplicate, delete with Undo.
  • Cycles (#/cycles/<id>): scope, completed, scope change and days left; a burn up chart in SVG (scope as a step line, completed as a filled step, a dashed projection at the current pace, today marker, hover or arrow keys to read any day, and a hidden table for screen readers); a scope change log (added, removed, re-estimated, by whom); the cycle's issues; roll unfinished issues into the next cycle; add the next cycle.
  • Search (#/search, or /): live search over titles, keys, descriptions and the comments you are allowed to see, with highlighted matches, filters for status, assignee, label and project, three sorts, and CSV export.
  • Command palette (Ctrl or Cmd K): create an issue, change the status or assignee of the focused issue (or pick an issue first), assign to me, jump to any project or issue, switch layout, theme or role, show shortcuts.
  • Settings (#/settings/...): workflow statuses (rename, category, reorder, add, delete by moving their issues), workspace name and cycle length; labels (rename, eight colours, add, delete with Undo); members (roles, invite with validation, which projects each guest sees, remove; the last admin is protected); preferences that change behaviour (theme, density, week start, due soon window, motion, single key shortcuts, sub tasks on boards, simulated activity); data (sample removal, CSV export, reset).

Keyboard: C create, A assign, S status, P priority, L labels, E estimate, D due date, R rename, / search, ? the shortcut list, G then I, M, P, Y or S to go somewhere, J and K to move, X to select, 1 2 3 for layouts, Ctrl or Cmd Z to undo the last change, Escape to close or go back.

What is demo or simulated

  • Sign in is a demo: any email works and nothing is checked. Connect Supabase Auth.
  • Invitations are recorded but no email is sent. Connect an email provider in a server function.
  • Teammate activity: shortly after a first visit, one teammate mentions you (labelled "Demo notification"), and a reminder lists what is due soon. Turn it off in Settings > Preferences.
  • There are no payments and no AI in this template.

Data model

schema.sql holds fifteen tables, all with row level security: workspaces, members (role admin, member or guest), statuses (with a category: backlog, unstarted, started, done, cancelled), labels, projects, project_shares (which guest sees which project), cycles, issues (numbered per project by a trigger, with parent for sub tasks), issue_labels, dependencies (loops refused by a trigger), comments (with internal), activity and cycle_events (append only), notifications and project_updates. It also has helper functions (pt_can_see_project and friends), guards (the last admin, guest requests, issue numbers, completion time) and the seed: the same sample workspace as demo mode, generated from buildSample() and dated relative to the day it runs.

Derived, never stored: project progress (done estimate over total, cancelled excluded), due soon, blocked, the burn up series (scope from cycle_events, completed from completed_at) and the projection.

The browser keeps one object, store.data, with the same shape as the tables, with two conveniences: an issue's labels are label_ids (the issue_labels rows) and a project's guests are shared_with (the project_shares rows).

Moving from localStorage to Supabase

Screens only read store.data. Every write goes through the domain functions in section 5 of script.js (createIssue, updateIssue, deleteIssue, addComment, notify, logActivity, cycleEvent) or through commit(), and all of them end in store.save(). Replace store.load() and store.save(), and send each change as it happens.

// 1. Sign in first, then load. The anon key is public by design; RLS protects the rows.
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);

store.load = async function () {
  const tables = ["workspaces", "members", "statuses", "labels", "projects", "project_shares", "cycles", "issues",
    "issue_labels", "dependencies", "comments", "activity", "notifications", "cycle_events", "project_updates"];
  const res = await Promise.all(tables.map((t) => supabase.from(t).select("*")));
  const failed = res.find((r) => r.error);
  if (failed) throw failed.error; // the error screen offers "Try again"
  const [ws, members, statuses, labels, projects, shares, cycles, issues, issueLabels, ...rest] = res.map((r) => r.data);
  this.data = {
    version: 1,
    workspace: ws[0],
    members, statuses, labels, cycles,
    projects: projects.map((p) => ({ ...p, shared_with: shares.filter((s) => s.project_id === p.id).map((s) => s.member_id) })),
    issues: issues.map((i) => ({ ...i, label_ids: issueLabels.filter((l) => l.issue_id === i.id).map((l) => l.label_id) })),
    // dependencies, comments, activity, notifications, cycle_events, project_updates follow as they are
  };
};

// 2. Writes stay optimistic: change the screen, send the change, undo it if the server says no.
//    Ask for the rows back and treat zero rows as a refusal: RLS refuses UPDATE and DELETE without an error.
const _update = updateIssue;
updateIssue = (id, patch) => {
  const changed = _update(id, patch);
  if (changed) supabase.from("issues").update(toColumns(patch)).eq("id", id).select().then(({ data, error }) => {
    if (error || !data?.length) { undoLast(); toast("That change was not saved. You may not have access."); }
  });
  return changed;
};
store.save = () => {};

Column names differ in three places: start and due are start_on and due_on, activity's from and to are from_value and to_value, and timestamps named at are created_at. Then: make boot() await store.load() (the skeleton in index.html shows until the first render); replace me() with the member whose user_id is the signed in user; delete the "Viewing as" control, buildSample() and simulateTeammate(). For live updates between teammates, subscribe with supabase.channel(...) to issues, comments and notifications. The issue number, completion time and a guest's request label are set by triggers, so read them back from the insert.

Server side functions you will need

  • Invite a member: insert the members row, email a sign in link, and on first sign in set user_id for the row whose email matches the confirmed address. Runs with the service role and must check the email is confirmed.
  • Notifications by email or push (mentions, assignments, due tomorrow): a scheduled function reading notifications and due dates, with the provider key on the server.
  • Due soon reminders for people who have not opened the app: the same scheduled function.

Files

FileWhat it holds
index.htmlShell: sidebar, top bar, loading skeleton, tab bar, New issue button, picker, dialog, palette, toasts, tips, live region
styles.cssTokens (light, dark), base, shell, components, each screen, overlays, motion, larger and smaller screens, print
script.jsHelpers, sample data, store, roles (can), domain, router and screens, board, list, timeline, issue and markdown, cycles and burn up, search, settings, overlays, keyboard, drag and drop, CSV and calendar export, tips, boot
schema.sqlTables, helper functions, guards, RLS, grants, the seed (same rows as demo mode), VERIFY block
template.jsonCatalogue entry and declared identity
media.jsonThe four photo prompts, one shared art direction

Accessibility

Skip link; each new screen moves focus to its heading; aria-current in navigation; tabs with aria-selected; labelled inputs with inline errors tied by aria-describedby; pickers and the palette are listboxes driven by aria-activedescendant; keyboard alternatives to every drag (board and timeline); moves, toasts and chart readings announced through a polite live region; the burn up chart has a data table for screen readers; confirmations happen inside the page with focus on the safe choice; nothing relies on colour alone (status glyph shapes, health words, dashed conflict lines); visible focus everywhere; motion off under reduced motion.

Known limits

  • The timeline draws every dated issue in a project; above a few hundred rows it wants virtualised rows.
  • Undo keeps the last change only, for the session.
  • The markdown is deliberately small (no tables or images) and only https links are allowed.