Let’s add one more “real” domain so this becomes a true work engine: Jobs / Work Items — intake → processing → completion → reporting All inside the same GitHub‑only architecture.
I’ll keep it tight and pattern‑driven so you can clone it for anything.
—
1. New record type: `Job`
Use case: any unit of work at your job—ticket, request, engagement, internal project.
1.1 Issue template in `issues-db`
.github/ISSUE_TEMPLATE/job.yml:
name: Job
description: Create a new job / work item
title: “job: ”
labels: [“record:job”, “status:queued”]
body:
– type: input
id: owner
attributes:
label: Owner
placeholder: “who is responsible?”
validations:
required: true
– type: dropdown
id: priority
attributes:
label: Priority
options:
– high
– medium
– low
default: 1
– type: textarea
id: details
attributes:
label: Details
placeholder: “Context, requirements, links”
– type: textarea
id: acceptance
attributes:
label: Acceptance Criteria
placeholder: “What does ‘done’ look like?”
—
2. Dispatch from `issues-db` → `backend-automation`
Extend on-issue.yml again:
dispatch-job:
if: contains(github.event.issue.labels.*.name, ‘record:job’) runs-on: ubuntu-latest
steps:
– name: Send job to backend-automation
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.GH_PAT }}
repository: max-github-system/backend-automation
event-type: job-record
client-payload: |
{
“number”: ${{ github.event.issue.number }},
“action”: “${{ github.event.action }}”,
“title”: “${{ github.event.issue.title }}”,
“state”: “${{ github.event.issue.state }}”,
“labels”: ${{ toJson(github.event.issue.labels) }},
“body”: ${{ toJson(github.event.issue.body) }}
}
—
3. Backend processor for `Job`
3.1 Workflow
backend-automation/.github/workflows/on-dispatch-job-record.yml:
name: Handle Job Records
on:
repository_dispatch:
types: [job-record]
jobs:
process-job:
runs-on: ubuntu-latest
steps:
– uses: actions/checkout@v4
– uses: actions/setup-node@v4
with:
node-version: 20
– name: Install deps
run: npm ci || true
– name: Process job
env:
PAYLOAD: ${{ toJson(github.event.client_payload) }}
GH_TOKEN: ${{ secrets.GH_PAT }}
run: node scripts/processJobRecord.mjs
3.2 `scripts/processJobRecord.mjs` (simple version)
import { Octokit } from ‘@octokit/rest’;
const payload = JSON.parse(process.env.PAYLOAD);
const octokit = new Octokit({ auth: process.env.GH_TOKEN });
function getLabelValue(labels, prefix) {
const label = labels.find(l => l.name.startsWith(prefix));
return label ? label.name.replace(prefix, ”) : null;
}
function mapStatus(labels, state) {
return getLabelValue(labels, ‘status:’) || (state === ‘closed’ ? ‘done’ : ‘queued’); }
function mapPriority(labels) {
return getLabelValue(labels, ‘priority:’) || ‘medium’;
}
async function main() {
const labels = payload.labels || [];
const status = mapStatus(labels, payload.state);
const priority = mapPriority(labels);
const job = {
id: payload.number,
title: payload.title,
status,
priority,
labels: labels.map(l => l.name),
rawBody: payload.body,
updatedAt: new Date().toISOString()
};
const content = Buffer.from(JSON.stringify(job, null, 2)).toString(‘base64’);
await octokit.repos.createOrUpdateFileContents({
owner: ‘max-github-system’,
repo: ‘data-hub’,
path: `data/jobs/${payload.number}.json`,
message: `chore: sync job #${payload.number}`,
content
});
}
main().catch(err => {
console.error(err);
process.exit(1);
});
—
4. Data layer for `Job`
In data-hub:
• Folder: data/jobs/
• Optional schema: schemas/jobs.schema.json
Example data/jobs/15.json:
{
“id”: 15,
“title”: “job: onboard new client”,
“status”: “queued”,
“priority”: “high”,
“labels”: [
“record:job”,
“status:queued”,
“priority:high”
],
“rawBody”: “### Owner\nray\n\n### Priority\nhigh\n\n### Details\nKickoff, access, initial architecture.\n\n### Acceptance Criteria\nClient has working environment and first deliverable.”, “updatedAt”: “2026-02-09T03:01:23.000Z”
}
—
5. Frontend: Jobs view
Reuse the same fetcher (fetchData.mjs)—it already pulls jobs if you add the folder.
Update fetchData.mjs:
const tasks = await fetchCollection(‘tasks’);
const users = await fetchCollection(‘users’);
const jobs = await fetchCollection(‘jobs’);
// …
fs.writeFileSync(path.join(outDir, ‘jobs.json’), JSON.stringify(jobs, null, 2));
Create src/Jobs.tsx:
import jobs from ‘./generated/jobs.json’;
import { useMemo, useState } from ‘react’;
type Job = {
id: number;
title: string;
status: string;
priority: string;
labels: string[];
rawBody: string;
updatedAt: string;
};
const STATUS_OPTIONS = [‘all’, ‘queued’, ‘in-progress’, ‘done’] as const;
export function Jobs() {
const typedJobs = jobs as Job[];
const [statusFilter, setStatusFilter] =
useState<(typeof STATUS_OPTIONS)[number]>(‘all’);
const filtered = useMemo(() => {
if (statusFilter === ‘all’) return typedJobs;
return typedJobs.filter(j => j.status === statusFilter);
}, [typedJobs, statusFilter]);
return (
Jobs
Status:
{STATUS_OPTIONS.map(s => (
setStatusFilter(s)}
style={{
marginRight: ‘0.5rem’,
padding: ‘0.25rem 0.75rem’,
borderRadius: ‘999px’,
border: s === statusFilter ? ‘2px solid #111’ : ‘1px solid #ccc’, background: s === statusFilter ? ‘#111’ : ‘#fff’,
color: s === statusFilter ? ‘#fff’ : ‘#111’,
cursor: ‘pointer’
}}
>
{s}
))}
- {filtered.map(job => (
- #{job.id} {job.title}
{job.rawBody}Updated: {new Date(job.updatedAt).toLocaleString()}
))}
);
}
function Badge({ label, kind }: { label: string; kind: ‘status’ | ‘priority’ }) { const normalized = label.toLowerCase();
let bg = ‘#eee’;
let color = ‘#111’;
if (kind === ‘status’) {
if (normalized === ‘queued’) bg = ‘#f5f5f5’;
if (normalized === ‘in-progress’) bg = ‘#fff3cd’;
if (normalized === ‘done’) bg = ‘#d1e7dd’;
}
if (kind === ‘priority’) {
if (normalized === ‘high’) bg = ‘#f8d7da’;
if (normalized === ‘medium’) bg = ‘#e2e3e5’;
if (normalized === ‘low’) bg = ‘#d1e7dd’;
}
return (
{label}
);
}
Update App tabs to include Jobs:
import { Tasks } from ‘./Tasks’;
import { Users } from ‘./Users’;
import { Jobs } from ‘./Jobs’;
type View = ‘tasks’ | ‘users’ | ‘jobs’;
// add Jobs button and view switch
—
6. What you’ve actually built
You now have a GitHub‑native work platform:
• Tasks → granular units of work
• Users → people/entities in the system
• Jobs → higher‑level work items / engagements
All:
• Created as Issues
• Mirrored as JSON
• Visible in a UI
• Enrichable by AI
• Running entirely on GitHub infra
From here, you can:
• Swap “Job” for “Audio Session”, “Quiz Attempt”, “Client Engagement”, etc.
• Or start layering reporting (e.g., ai-workflows generating dashboards into data-hub and a “Reports” view in frontend-app).
If you tell me the actual domain you want this to represent at work (e.g., “client projects”, “internal automation tasks”, “content pipeline”), I can map these three record types directly onto that reality and tune the fields to match.