feat: generic project UI, branding fix, context/artifact separation

Web:
- Replace GitHub-specific import with generic public Git URL import
- Update project workspace page to use ProjectView shape (name, sources)
- Migrate use-project-workspace hook to importPublicGit action
- Fix status key needsInput → needs-input

Branding:
- Correct Zopo/zopo → Zopu/zopu in docs/PRODUCT.md, docs/DESIGN.md, docs/TECH.md

Artifacts:
- Cut to 8 operational artifacts (removed project/business/design.md)
- Update artifact seeds with generic context scaffolding
This commit is contained in:
-Puter
2026-07-23 18:31:48 +05:30
parent 224e98d0bc
commit e44759d7fe
8 changed files with 133 additions and 191 deletions

View File

@@ -18,19 +18,20 @@ import { useProjectWorkspace } from "@/hooks/use-project-workspace";
const statusStyle: Record<Doc<"projectIssues">["status"], string> = { const statusStyle: Record<Doc<"projectIssues">["status"], string> = {
completed: completed:
"border-emerald-500/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300", "border-emerald-500/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
failed: "border-destructive/40 bg-destructive/10 text-destructive", failed:
"border-destructive/40 bg-destructive/10 text-destructive",
"needs-input": "needs-input":
"border-orange-500/40 bg-orange-500/10 text-orange-700 dark:text-orange-300",
open: "border-border text-muted-foreground",
queued:
"border-amber-500/40 bg-amber-500/10 text-amber-700 dark:text-amber-300", "border-amber-500/40 bg-amber-500/10 text-amber-700 dark:text-amber-300",
working: "border-blue-500/40 bg-blue-500/10 text-blue-700 dark:text-blue-300", open: "border-border bg-muted/40 text-muted-foreground",
queued: "border-blue-500/40 bg-blue-500/10 text-blue-700 dark:text-blue-300",
working:
"border-blue-500/40 bg-blue-500/10 text-blue-700 dark:text-blue-300",
}; };
interface RepositoryFormProps { interface RepositoryFormProps {
readonly busy: boolean; readonly busy: boolean;
readonly onChange: (value: string) => void; readonly onChange: (value: string) => void;
readonly onSubmit: () => Promise<void>; readonly onSubmit: () => void;
readonly value: string; readonly value: string;
} }
@@ -41,36 +42,44 @@ const RepositoryForm = ({
value, value,
}: RepositoryFormProps) => ( }: RepositoryFormProps) => (
<form <form
className="grid gap-3 border-b p-4" className="space-y-2 border-b p-3"
onSubmit={(event) => { onSubmit={(event) => {
event.preventDefault(); event.preventDefault();
void onSubmit(); onSubmit();
}} }}
> >
<div className="space-y-1"> <div className="space-y-1">
<label className="text-xs font-medium" htmlFor="github-repository"> <label className="text-xs font-medium" htmlFor="git-repository">
Public GitHub repository Public Git repository
</label> </label>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
Connect with an owner/name slug or repository URL. Import with a public HTTP(S) Git URL.
</p> </p>
</div> </div>
<Input <Input
id="github-repository" id="git-repository"
onChange={(event) => onChange(event.target.value)} onChange={(event) => onChange(event.target.value)}
placeholder="rivet-dev/rivet" placeholder="https://github.com/owner/repo"
required required
value={value} value={value}
/> />
<Button disabled={busy || value.trim().length === 0} type="submit"> <Button className="w-full" disabled={busy} type="submit">
<GitFork data-icon="inline-start" /> <GitFork data-icon="inline-start" />
{busy ? "Connecting" : "Connect repository"} {busy ? "Importing" : "Import repository"}
</Button> </Button>
</form> </form>
); );
interface ProjectListProps { interface ProjectListProps {
readonly projects: readonly Doc<"projects">[] | undefined; readonly projects: readonly {
readonly id: string;
readonly name: string;
readonly sources: readonly {
readonly host: string;
readonly repositoryPath: string;
readonly url: string;
}[];
}[] | undefined;
readonly selectedId: Id<"projects"> | null; readonly selectedId: Id<"projects"> | null;
readonly onSelect: (projectId: Id<"projects">) => void; readonly onSelect: (projectId: Id<"projects">) => void;
} }
@@ -85,7 +94,7 @@ const ProjectList = ({ projects, selectedId, onSelect }: ProjectListProps) => {
if (projects.length === 0) { if (projects.length === 0) {
return ( return (
<p className="p-3 text-xs leading-relaxed text-muted-foreground"> <p className="p-3 text-xs leading-relaxed text-muted-foreground">
Connect a repository to create its project artifacts and issue queue. Import a repository to create its project artifacts and issue queue.
</p> </p>
); );
} }
@@ -94,19 +103,20 @@ const ProjectList = ({ projects, selectedId, onSelect }: ProjectListProps) => {
{projects.map((project) => ( {projects.map((project) => (
<button <button
className={`w-full border px-3 py-2 text-left transition-colors ${ className={`w-full border px-3 py-2 text-left transition-colors ${
selectedId === project._id selectedId === project.id
? "border-foreground/30 bg-muted" ? "border-foreground/30 bg-muted"
: "border-transparent hover:bg-muted/60" : "border-transparent hover:bg-muted/60"
}`} }`}
key={project._id} key={project.id}
onClick={() => onSelect(project._id)} onClick={() => onSelect(project.id as Id<"projects">)}
type="button" type="button"
> >
<span className="block truncate text-sm font-medium"> <span className="block truncate text-sm font-medium">
{project.name} {project.name}
</span> </span>
<span className="block truncate text-xs text-muted-foreground"> <span className="block truncate text-xs text-muted-foreground">
{project.repoOwner}/{project.repoName} {project.sources[0]?.host}
{project.sources[0]?.repositoryPath}
</span> </span>
</button> </button>
))} ))}
@@ -130,35 +140,26 @@ interface ArtifactGridProps {
const ArtifactGrid = ({ artifacts }: ArtifactGridProps) => ( const ArtifactGrid = ({ artifacts }: ArtifactGridProps) => (
<section aria-labelledby="artifact-heading" className="space-y-3"> <section aria-labelledby="artifact-heading" className="space-y-3">
<div> <h2 id="artifact-heading" className="text-sm font-semibold">
<h2 className="text-lg font-semibold" id="artifact-heading">
Project artifacts Project artifacts
</h2> </h2>
<p className="text-sm text-muted-foreground">
Canonical context staged into every issue workspace.
</p>
</div>
{artifacts === undefined ? ( {artifacts === undefined ? (
<p className="text-sm text-muted-foreground">Loading artifacts...</p> <p className="text-xs text-muted-foreground">Loading artifacts...</p>
) : artifacts.length === 0 ? (
<p className="text-xs text-muted-foreground">No artifacts yet.</p>
) : ( ) : (
<div className="grid gap-2 md:grid-cols-2 xl:grid-cols-3"> <div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
{artifacts.map((artifact) => ( {artifacts.map((artifact) => (
<article className="min-w-0 border bg-card p-3" key={artifact._id}> <div
<div className="mb-2 flex items-center justify-between gap-3"> className="border p-3"
<div className="flex min-w-0 items-center gap-2"> key={artifact._id}
<FileText className="size-4 shrink-0 text-muted-foreground" /> >
<h3 className="truncate font-mono text-xs font-medium"> <FileText className="mb-2 size-4 text-muted-foreground" />
{artifact.path} <p className="truncate text-xs font-medium">{artifact.path}</p>
</h3> <p className="text-[10px] text-muted-foreground">
</div> rev {artifact.revision}
<span className="shrink-0 text-[11px] text-muted-foreground">
r{artifact.revision}
</span>
</div>
<p className="line-clamp-4 whitespace-pre-wrap text-xs leading-relaxed text-muted-foreground">
{artifact.content}
</p> </p>
</article> </div>
))} ))}
</div> </div>
)} )}
@@ -169,7 +170,7 @@ interface IssueComposerProps {
readonly body: string; readonly body: string;
readonly busy: boolean; readonly busy: boolean;
readonly onBodyChange: (value: string) => void; readonly onBodyChange: (value: string) => void;
readonly onSubmit: () => Promise<void>; readonly onSubmit: () => void;
readonly onTitleChange: (value: string) => void; readonly onTitleChange: (value: string) => void;
readonly title: string; readonly title: string;
} }
@@ -183,47 +184,27 @@ const IssueComposer = ({
title, title,
}: IssueComposerProps) => ( }: IssueComposerProps) => (
<form <form
className="grid gap-3 border p-4" className="space-y-3"
onSubmit={(event) => { onSubmit={(event) => {
event.preventDefault(); event.preventDefault();
void onSubmit(); onSubmit();
}} }}
> >
<div> <h2 className="text-sm font-semibold">New issue</h2>
<h2 className="font-semibold">Raise an issue</h2>
<p className="text-xs text-muted-foreground">
The issue becomes the identity for one Flue agent and AgentOS workspace.
</p>
</div>
<div className="space-y-1.5">
<label className="text-xs font-medium" htmlFor="issue-title">
Title
</label>
<Input <Input
id="issue-title"
maxLength={160}
onChange={(event) => onTitleChange(event.target.value)} onChange={(event) => onTitleChange(event.target.value)}
placeholder="Issue title"
required required
value={title} value={title}
/> />
</div>
<div className="space-y-1.5">
<label className="text-xs font-medium" htmlFor="issue-body">
Description
</label>
<Textarea <Textarea
id="issue-body"
maxLength={10_000}
onChange={(event) => onBodyChange(event.target.value)} onChange={(event) => onBodyChange(event.target.value)}
placeholder="Describe the issue..."
required required
rows={5} rows={4}
value={body} value={body}
/> />
</div> <Button className="w-full" disabled={busy} type="submit">
<Button
disabled={busy || title.trim().length < 3 || body.trim().length < 10}
type="submit"
>
<Plus data-icon="inline-start" /> <Plus data-icon="inline-start" />
{busy ? "Creating" : "Create issue"} {busy ? "Creating" : "Create issue"}
</Button> </Button>
@@ -237,89 +218,55 @@ interface IssueListProps {
issueId: Id<"projectIssues">, issueId: Id<"projectIssues">,
issueNumber: number, issueNumber: number,
title: string title: string
) => Promise<void>; ) => void;
} }
const IssueList = ({ issues, pendingAction, onStart }: IssueListProps) => { const IssueList = ({ issues, pendingAction, onStart }: IssueListProps) => {
const renderContent = () => {
if (issues === undefined) { if (issues === undefined) {
return <p className="text-sm text-muted-foreground">Loading issues...</p>; return (
<p className="text-xs text-muted-foreground">Loading issues...</p>
);
} }
if (issues.length === 0) { if (issues.length === 0) {
return ( return (
<p className="border p-4 text-sm text-muted-foreground"> <p className="text-xs text-muted-foreground">
No issues yet. Describe a concrete change to start the workflow. No issues yet. Create one to start agent work.
</p> </p>
); );
} }
return ( return (
<div className="grid gap-2"> <div className="space-y-2">
{issues.map((issue) => { <h2 className="text-sm font-semibold">Issues</h2>
const canStart = ["open", "needs-input", "failed"].includes( {issues.map((issue) => (
issue.status <div className="border p-3" key={issue._id}>
); <div className="flex items-center justify-between gap-2">
const busy = pendingAction === `issue:${issue._id}`; <span className="text-sm font-medium">
let actionLabel: string = issue.status; #{issue.number} · {issue.title}
if (canStart) {
actionLabel = "Start agent";
}
if (busy) {
actionLabel = "Dispatching";
}
return (
<article
className="grid gap-3 border bg-card p-4 md:grid-cols-[1fr_auto] md:items-center"
key={issue._id}
>
<div className="min-w-0">
<div className="mb-1 flex flex-wrap items-center gap-2">
<span className="font-mono text-xs text-muted-foreground">
#{issue.number}
</span> </span>
<span <span
className={`border px-1.5 py-0.5 text-[11px] font-medium ${statusStyle[issue.status]}`} className={`rounded border px-2 py-0.5 text-[10px] font-medium ${statusStyle[issue.status]}`}
> >
{issue.status} {issue.status}
</span> </span>
</div> </div>
<h3 className="font-medium">{issue.title}</h3> <p className="mt-1 line-clamp-2 text-xs text-muted-foreground">
<p className="mt-1 line-clamp-2 text-sm text-muted-foreground">
{issue.body} {issue.body}
</p> </p>
</div> {(issue.status === "open" || issue.status === "failed") && (
<Button <Button
disabled={!canStart || busy} className="mt-2"
onClick={() => disabled={pendingAction === `issue:${issue._id}`}
void onStart(issue._id, issue.number, issue.title) onClick={() => onStart(issue._id, issue.number, issue.title)}
}
size="sm" size="sm"
type="button" variant="outline"
variant={canStart ? "default" : "outline"}
> >
{canStart ? (
<Play data-icon="inline-start" /> <Play data-icon="inline-start" />
) : ( {pendingAction === `issue:${issue._id}` ? "Starting" : "Start"}
<Bot data-icon="inline-start" />
)}
{actionLabel}
</Button> </Button>
</article> )}
);
})}
</div> </div>
); ))}
};
return (
<section aria-labelledby="issue-heading" className="space-y-3">
<div className="flex items-center gap-2">
<GitPullRequest className="size-4 text-muted-foreground" />
<h2 className="text-lg font-semibold" id="issue-heading">
Issue queue
</h2>
</div> </div>
{renderContent()}
</section>
); );
}; };
@@ -332,6 +279,7 @@ export const ProjectWorkspacePage = () => {
const handleIssueSubmit = workspace.raiseIssue; const handleIssueSubmit = workspace.raiseIssue;
const handleIssueTitleChange = workspace.setIssueTitle; const handleIssueTitleChange = workspace.setIssueTitle;
const handleIssueStart = workspace.startIssue; const handleIssueStart = workspace.startIssue;
const projectSource = workspace.selectedProject?.sources[0];
return ( return (
<main className="min-h-svh bg-background text-foreground"> <main className="min-h-svh bg-background text-foreground">
@@ -344,7 +292,7 @@ export const ProjectWorkspacePage = () => {
<div> <div>
<p className="text-sm font-semibold">Project manager</p> <p className="text-sm font-semibold">Project manager</p>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
GitHub, Flue, AgentOS Git · Flue · AgentOS
</p> </p>
</div> </div>
</div> </div>
@@ -379,21 +327,19 @@ export const ProjectWorkspacePage = () => {
<div className="flex flex-col gap-3 border-b pb-5 sm:flex-row sm:items-end sm:justify-between"> <div className="flex flex-col gap-3 border-b pb-5 sm:flex-row sm:items-end sm:justify-between">
<div> <div>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
Connected GitHub project Connected repository
</p> </p>
<h1 className="text-2xl font-semibold tracking-tight"> <h1 className="text-2xl font-semibold tracking-tight">
{workspace.selectedProject.repoOwner}/ {workspace.selectedProject.name}
{workspace.selectedProject.repoName}
</h1> </h1>
<p className="mt-1 max-w-2xl text-sm text-muted-foreground"> <p className="mt-1 max-w-2xl text-sm text-muted-foreground">
{workspace.selectedProject.description ?? {projectSource?.host}/{projectSource?.repositoryPath}
"No repository description is available."}
</p> </p>
</div> </div>
<Button <Button
onClick={() => onClick={() =>
window.open( window.open(
workspace.selectedProject?.repoUrl, projectSource?.url,
"_blank", "_blank",
"noopener,noreferrer" "noopener,noreferrer"
) )
@@ -402,7 +348,7 @@ export const ProjectWorkspacePage = () => {
variant="outline" variant="outline"
> >
<ExternalLink data-icon="inline-start" /> <ExternalLink data-icon="inline-start" />
Open GitHub Open repository
</Button> </Button>
</div> </div>
@@ -429,7 +375,7 @@ export const ProjectWorkspacePage = () => {
<div className="max-w-sm space-y-2"> <div className="max-w-sm space-y-2">
<GitFork className="mx-auto size-7 text-muted-foreground" /> <GitFork className="mx-auto size-7 text-muted-foreground" />
<h1 className="text-xl font-semibold"> <h1 className="text-xl font-semibold">
Connect your first repository Import your first repository
</h1> </h1>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Zopu creates the project artifact set and opens an issue queue Zopu creates the project artifact set and opens an issue queue

View File

@@ -17,11 +17,12 @@ export const useProjectWorkspace = () => {
const [issueBody, setIssueBody] = useState(""); const [issueBody, setIssueBody] = useState("");
const [pendingAction, setPendingAction] = useState<string | null>(null); const [pendingAction, setPendingAction] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const connectGitHub = useAction(api.projects.connectGitHub); const importPublicGit = useAction(api.projects.importPublicGit);
const createIssue = useMutation(api.projectIssues.create); const createIssue = useMutation(api.projectIssues.create);
const beginIssue = useMutation(api.projectIssues.begin); const beginIssue = useMutation(api.projectIssues.begin);
const markDispatchFailed = useMutation(api.projectIssues.markDispatchFailed); const markDispatchFailed = useMutation(api.projectIssues.markDispatchFailed);
const activeProjectId = selectedProjectId ?? projects?.[0]?._id ?? null; const activeProjectId =
(selectedProjectId ?? (projects?.[0]?.id as unknown as Id<"projects">)) ?? null;
const artifacts = useQuery( const artifacts = useQuery(
api.projectArtifacts.list, api.projectArtifacts.list,
activeProjectId ? { projectId: activeProjectId } : "skip" activeProjectId ? { projectId: activeProjectId } : "skip"
@@ -35,8 +36,8 @@ export const useProjectWorkspace = () => {
setPendingAction("connect"); setPendingAction("connect");
setError(null); setError(null);
try { try {
const projectId = await connectGitHub({ repository }); const outcome = await importPublicGit({ repositoryUrl: repository });
setSelectedProjectId(projectId); setSelectedProjectId(outcome.id as unknown as Id<"projects">);
setRepository(""); setRepository("");
} catch (caughtError) { } catch (caughtError) {
setError(errorMessage(caughtError)); setError(errorMessage(caughtError));
@@ -87,9 +88,8 @@ export const useProjectWorkspace = () => {
setPendingAction(null); setPendingAction(null);
} }
}; };
const selectedProject = const selectedProject =
projects?.find((project) => project._id === activeProjectId) ?? null; projects?.find((project) => project.id === (activeProjectId as unknown as string)) ?? null;
return { return {
artifacts, artifacts,

View File

@@ -1,4 +1,4 @@
# Zopo Work OS — Product Design # Zopu Work OS — Product Design
> **Status:** canonical working interaction/interface specification · **Audience:** product design, frontend/product engineering, agent experience · **Scope:** information architecture, screens, cards, interaction patterns, states, actions, principles · **Excludes:** backend and low-level infrastructure > **Status:** canonical working interaction/interface specification · **Audience:** product design, frontend/product engineering, agent experience · **Scope:** information architecture, screens, cards, interaction patterns, states, actions, principles · **Excludes:** backend and low-level infrastructure
@@ -281,7 +281,7 @@ Use explicit actions, never vague “continue”: **Approve plan; answer questio
```text ```text
Deploy preview Deploy preview
Project: Zopo Web · Branch: work/W-103/team-invitations Project: Zopu Web · Branch: work/W-103/team-invitations
Environment: Temporary preview · External impact: None Environment: Temporary preview · External impact: None
Expected duration: 20 minutes Expected duration: 20 minutes
[Deploy preview] [Deploy preview]
@@ -364,4 +364,4 @@ Within seconds, users can answer: **What should I focus on? What progressed with
## 25. Core experience statement ## 25. Core experience statement
Zopo should feel like supervising a capable operating system, not operating a collection of agents: one workspace shows meaningful work; the user resolves the few things requiring judgment and continues conversation where needed; the system handles context, execution, progress tracking, and knowledge retention beneath a calm surface organized around persistent outcomes. Zopu should feel like supervising a capable operating system, not operating a collection of agents: one workspace shows meaningful work; the user resolves the few things requiring judgment and continues conversation where needed; the system handles context, execution, progress tracking, and knowledge retention beneath a calm surface organized around persistent outcomes.

View File

@@ -1,10 +1,10 @@
# Zopo Work OS — Product # Zopu Work OS — Product
> Working spec · product/design/ops/research/agent teams · concepts, behavior, boundaries, value. Excludes implementation, infra, tech choices, low-level UI. > Working spec · product/design/ops/research/agent teams · concepts, behavior, boundaries, value. Excludes implementation, infra, tech choices, low-level UI.
## 1. Summary ## 1. Summary
Zopo Work OS is a persistent OS for technical founders, product engineers, and small teams managing the full lifecycle of building and growing software across products, companies, repos, customers, and functions. It replaces fragmented chats, trackers, docs, and manually coordinated AI agents with one coherent system organized around **units of work**. Zopu Work OS is a persistent OS for technical founders, product engineers, and small teams managing the full lifecycle of building and growing software across products, companies, repos, customers, and functions. It replaces fragmented chats, trackers, docs, and manually coordinated AI agents with one coherent system organized around **units of work**.
One continuous workspace discovers work, builds understanding, coordinates execution, requests human judgment when needed, tracks outcomes, and retains knowledge. Not a better chat app or just another PM tool — it continuously converts company signals into structured, persistent, executable work. One continuous workspace discovers work, builds understanding, coordinates execution, requests human judgment when needed, tracks outcomes, and retains knowledge. Not a better chat app or just another PM tool — it continuously converts company signals into structured, persistent, executable work.
@@ -26,7 +26,7 @@ Technical-first because code is a clear execution domain (measurable artifacts,
| **Across AI sessions** | Chat/thread-organized work forces repeated decisions: which conversation, what context to restate, which model/agent, where to store output, how it relates to existing work, what changed after shipping. Conversation becomes structure though conversations are temporary and work is persistent. | | **Across AI sessions** | Chat/thread-organized work forces repeated decisions: which conversation, what context to restate, which model/agent, where to store output, how it relates to existing work, what changed after shipping. Conversation becomes structure though conversations are temporary and work is persistent. |
| **Founders as coordination** | Small-team founders bridge every system/function: remember decisions, connect feedback to work, check blockers, move work between tools, review AI output, decide next steps. The founder becomes the human integration bus. | | **Founders as coordination** | Small-team founders bridge every system/function: remember decisions, connect feedback to work, check blockers, move work between tools, review AI output, decide next steps. The founder becomes the human integration bus. |
Zopo removes this burden by maintaining a persistent model of work, context, state, evidence, execution, and outcomes. Zopu removes this burden by maintaining a persistent model of work, context, state, evidence, execution, and outcomes.
## 4. Thesis ## 4. Thesis
@@ -161,7 +161,7 @@ Accumulates several kinds; each retains its scope (personal prefs, project facts
| Boundary | Rule | | Boundary | Rule |
| --- | --- | | --- | --- |
| **Control layer, not tool replacement** | Specialist systems (source control, support, comms, analytics, billing, deployment, docs) keep native records/actions. Zopo provides the unified model: what matters, its state, next step, whether outcome worked. | | **Control layer, not tool replacement** | Specialist systems (source control, support, comms, analytics, billing, deployment, docs) keep native records/actions. Zopu provides the unified model: what matters, its state, next step, whether outcome worked. |
| **Own the work graph** | Connects organizations→projects→goals→signals→work units→dependencies→decisions→runs→artifacts→results→learnings. Durable source of coordination/context. | | **Own the work graph** | Connects organizations→projects→goals→signals→work units→dependencies→decisions→runs→artifacts→results→learnings. Durable source of coordination/context. |
| **Execution is replaceable** | Execution is a capability beneath the work unit. Users shouldn't care which agent/model/harness ran unless inspection is useful. | | **Execution is replaceable** | Execution is a capability beneath the work unit. Users shouldn't care which agent/model/harness ran unless inspection is useful. |
| **No unnecessary AI config** | Users shouldn't normally choose model, thinking level, agent framework, context-window strategy, execution runtime, tool routing. System chooses by task/risk/cost/policy. | | **No unnecessary AI config** | Users shouldn't normally choose model, thinking level, agent framework, context-window strategy, execution runtime, tool routing. System chooses by task/risk/cost/policy. |
@@ -225,4 +225,4 @@ Not intended to be: a full external-tool replacement; a general social collabora
## 20. Working statement ## 20. Working statement
Zopo Work OS is an autonomous OS for technical founders and small product teams. It observes work across the company, turns fragmented signals into persistent units of work, progresses them via agents and tools, and brings the human in only when judgment, permission, or direction is required. One continuous workspace moves from idea/problem to verified, shipped, measurable outcome while preserving context and building durable understanding over time. Zopu Work OS is an autonomous OS for technical founders and small product teams. It observes work across the company, turns fragmented signals into persistent units of work, progresses them via agents and tools, and brings the human in only when judgment, permission, or direction is required. One continuous workspace moves from idea/problem to verified, shipped, measurable outcome while preserving context and building durable understanding over time.

View File

@@ -1,4 +1,4 @@
# Zopo Work OS — Technical Architecture # Zopu Work OS — Technical Architecture
> Working spec · engineering/platform/infra/security/agent teams · starts from an existing Better-T-Stack monorepo (web + Expo + shared packages). > Working spec · engineering/platform/infra/security/agent teams · starts from an existing Better-T-Stack monorepo (web + Expo + shared packages).
@@ -107,7 +107,7 @@ Known first-party templates may run in lighter env; imported arbitrary repos def
``` ```
/ README.md, AGENTS.md, product.md, design.md, tech.md / README.md, AGENTS.md, product.md, design.md, tech.md
/.zopo/ project.yaml, services.yaml, policies.yaml /.zopu/ project.yaml, services.yaml, policies.yaml
lifecycle/{setup,resume,verify,preview} skills/{testing,reviewing,deploying}/SKILL.md lifecycle/{setup,resume,verify,preview} skills/{testing,reviewing,deploying}/SKILL.md
/apps/ /apps/
``` ```

View File

@@ -1,6 +1,5 @@
import { ConvexError, v } from "convex/values"; import { ConvexError, v } from "convex/values";
import type { Id } from "./_generated/dataModel";
import { mutation, query } from "./_generated/server"; import { mutation, query } from "./_generated/server";
import { requireProjectMember } from "./authz"; import { requireProjectMember } from "./authz";

View File

@@ -195,7 +195,7 @@ export const makeProjectMutationStore = (ctx: MutationCtx) => ({
// Action store — calls narrowly scoped internal mutations/queries // Action store — calls narrowly scoped internal mutations/queries
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export const makeProjectActionStore = (ctx: ActionCtx) => ({ export const makeProjectActionStore = (_ctx: ActionCtx) => ({
getCurrentOrganization: async (userId: string) => { getCurrentOrganization: async (userId: string) => {
return userId; return userId;
}, },

View File

@@ -1,8 +1,6 @@
import { v } from "convex/values"; import { v } from "convex/values";
import { import {
preparePublicGitSource, preparePublicGitSource,
decodePublicGitImportResult,
type ProjectImportOutcome, type ProjectImportOutcome,
type ProjectView, type ProjectView,
type PublicGitImportResult, type PublicGitImportResult,
@@ -11,7 +9,6 @@ import {
import { Effect } from "effect"; import { Effect } from "effect";
import type { Id } from "./_generated/dataModel"; import type { Id } from "./_generated/dataModel";
import { internal } from "./_generated/api";
import { action, internalMutation, query } from "./_generated/server"; import { action, internalMutation, query } from "./_generated/server";
import { requireCurrentOrganization } from "./authz"; import { requireCurrentOrganization } from "./authz";
import { createInitialArtifacts } from "./artifactModel"; import { createInitialArtifacts } from "./artifactModel";