Merge pull request #990 from paperclipai/dotta-sunday-ui-updates
dottas-sunday-ui-updates
This commit is contained in:
201
.agents/skills/doc-maintenance/SKILL.md
Normal file
201
.agents/skills/doc-maintenance/SKILL.md
Normal file
@@ -0,0 +1,201 @@
|
||||
---
|
||||
name: doc-maintenance
|
||||
description: >
|
||||
Audit top-level documentation (README, SPEC, PRODUCT) against recent git
|
||||
history to find drift — shipped features missing from docs or features
|
||||
listed as upcoming that already landed. Proposes minimal edits, creates
|
||||
a branch, and opens a PR. Use when asked to review docs for accuracy,
|
||||
after major feature merges, or on a periodic schedule.
|
||||
---
|
||||
|
||||
# Doc Maintenance Skill
|
||||
|
||||
Detect documentation drift and fix it via PR — no rewrites, no churn.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Periodic doc review (e.g. weekly or after releases)
|
||||
- After major feature merges
|
||||
- When asked "are our docs up to date?"
|
||||
- When asked to audit README / SPEC / PRODUCT accuracy
|
||||
|
||||
## Target Documents
|
||||
|
||||
| Document | Path | What matters |
|
||||
|----------|------|-------------|
|
||||
| README | `README.md` | Features table, roadmap, quickstart, "what is" accuracy, "works with" table |
|
||||
| SPEC | `doc/SPEC.md` | No false "not supported" claims, major model/schema accuracy |
|
||||
| PRODUCT | `doc/PRODUCT.md` | Core concepts, feature list, principles accuracy |
|
||||
|
||||
Out of scope: DEVELOPING.md, DATABASE.md, CLI.md, doc/plans/, skill files,
|
||||
release notes. These are dev-facing or ephemeral — lower risk of user-facing
|
||||
confusion.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1 — Detect what changed
|
||||
|
||||
Find the last review cursor:
|
||||
|
||||
```bash
|
||||
# Read the last-reviewed commit SHA
|
||||
CURSOR_FILE=".doc-review-cursor"
|
||||
if [ -f "$CURSOR_FILE" ]; then
|
||||
LAST_SHA=$(cat "$CURSOR_FILE" | head -1)
|
||||
else
|
||||
# First run: look back 60 days
|
||||
LAST_SHA=$(git log --format="%H" --after="60 days ago" --reverse | head -1)
|
||||
fi
|
||||
```
|
||||
|
||||
Then gather commits since the cursor:
|
||||
|
||||
```bash
|
||||
git log "$LAST_SHA"..HEAD --oneline --no-merges
|
||||
```
|
||||
|
||||
### Step 2 — Classify changes
|
||||
|
||||
Scan commit messages and changed files. Categorize into:
|
||||
|
||||
- **Feature** — new capabilities (keywords: `feat`, `add`, `implement`, `support`)
|
||||
- **Breaking** — removed/renamed things (keywords: `remove`, `breaking`, `drop`, `rename`)
|
||||
- **Structural** — new directories, config changes, new adapters, new CLI commands
|
||||
|
||||
**Ignore:** refactors, test-only changes, CI config, dependency bumps, doc-only
|
||||
changes, style/formatting commits. These don't affect doc accuracy.
|
||||
|
||||
For borderline cases, check the actual diff — a commit titled "refactor: X"
|
||||
that adds a new public API is a feature.
|
||||
|
||||
### Step 3 — Build a change summary
|
||||
|
||||
Produce a concise list like:
|
||||
|
||||
```
|
||||
Since last review (<sha>, <date>):
|
||||
- FEATURE: Plugin system merged (runtime, SDK, CLI, slots, event bridge)
|
||||
- FEATURE: Project archiving added
|
||||
- BREAKING: Removed legacy webhook adapter
|
||||
- STRUCTURAL: New .agents/skills/ directory convention
|
||||
```
|
||||
|
||||
If there are no notable changes, skip to Step 7 (update cursor and exit).
|
||||
|
||||
### Step 4 — Audit each target doc
|
||||
|
||||
For each target document, read it fully and cross-reference against the change
|
||||
summary. Check for:
|
||||
|
||||
1. **False negatives** — major shipped features not mentioned at all
|
||||
2. **False positives** — features listed as "coming soon" / "roadmap" / "planned"
|
||||
/ "not supported" / "TBD" that already shipped
|
||||
3. **Quickstart accuracy** — install commands, prereqs, and startup instructions
|
||||
still correct (README only)
|
||||
4. **Feature table accuracy** — does the features section reflect current
|
||||
capabilities? (README only)
|
||||
5. **Works-with accuracy** — are supported adapters/integrations listed correctly?
|
||||
|
||||
Use `references/audit-checklist.md` as the structured checklist.
|
||||
Use `references/section-map.md` to know where to look for each feature area.
|
||||
|
||||
### Step 5 — Create branch and apply minimal edits
|
||||
|
||||
```bash
|
||||
# Create a branch for the doc updates
|
||||
BRANCH="docs/maintenance-$(date +%Y%m%d)"
|
||||
git checkout -b "$BRANCH"
|
||||
```
|
||||
|
||||
Apply **only** the edits needed to fix drift. Rules:
|
||||
|
||||
- **Minimal patches only.** Fix inaccuracies, don't rewrite sections.
|
||||
- **Preserve voice and style.** Match the existing tone of each document.
|
||||
- **No cosmetic changes.** Don't fix typos, reformat tables, or reorganize
|
||||
sections unless they're part of a factual fix.
|
||||
- **No new sections.** If a feature needs a whole new section, note it in the
|
||||
PR description as a follow-up — don't add it in a maintenance pass.
|
||||
- **Roadmap items:** Move shipped features out of Roadmap. Add a brief mention
|
||||
in the appropriate existing section if there isn't one already. Don't add
|
||||
long descriptions.
|
||||
|
||||
### Step 6 — Open a PR
|
||||
|
||||
Commit the changes and open a PR:
|
||||
|
||||
```bash
|
||||
git add README.md doc/SPEC.md doc/PRODUCT.md .doc-review-cursor
|
||||
git commit -m "docs: update documentation for accuracy
|
||||
|
||||
- [list each fix briefly]
|
||||
|
||||
Co-Authored-By: Paperclip <noreply@paperclip.ing>"
|
||||
|
||||
git push -u origin "$BRANCH"
|
||||
|
||||
gh pr create \
|
||||
--title "docs: periodic documentation accuracy update" \
|
||||
--body "$(cat <<'EOF'
|
||||
## Summary
|
||||
Automated doc maintenance pass. Fixes documentation drift detected since
|
||||
last review.
|
||||
|
||||
### Changes
|
||||
- [list each fix]
|
||||
|
||||
### Change summary (since last review)
|
||||
- [list notable code changes that triggered doc updates]
|
||||
|
||||
## Review notes
|
||||
- Only factual accuracy fixes — no style/cosmetic changes
|
||||
- Preserves existing voice and structure
|
||||
- Larger doc additions (new sections, tutorials) noted as follow-ups
|
||||
|
||||
🤖 Generated by doc-maintenance skill
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
### Step 7 — Update the cursor
|
||||
|
||||
After a successful audit (whether or not edits were needed), update the cursor:
|
||||
|
||||
```bash
|
||||
git rev-parse HEAD > .doc-review-cursor
|
||||
```
|
||||
|
||||
If edits were made, this is already committed in the PR branch. If no edits
|
||||
were needed, commit the cursor update to the current branch.
|
||||
|
||||
## Change Classification Rules
|
||||
|
||||
| Signal | Category | Doc update needed? |
|
||||
|--------|----------|-------------------|
|
||||
| `feat:`, `add`, `implement`, `support` in message | Feature | Yes if user-facing |
|
||||
| `remove`, `drop`, `breaking`, `!:` in message | Breaking | Yes |
|
||||
| New top-level directory or config file | Structural | Maybe |
|
||||
| `fix:`, `bugfix` | Fix | No (unless it changes behavior described in docs) |
|
||||
| `refactor:`, `chore:`, `ci:`, `test:` | Maintenance | No |
|
||||
| `docs:` | Doc change | No (already handled) |
|
||||
| Dependency bumps only | Maintenance | No |
|
||||
|
||||
## Patch Style Guide
|
||||
|
||||
- Fix the fact, not the prose
|
||||
- If removing a roadmap item, don't leave a gap — remove the bullet cleanly
|
||||
- If adding a feature mention, match the format of surrounding entries
|
||||
(e.g. if features are in a table, add a table row)
|
||||
- Keep README changes especially minimal — it shouldn't churn often
|
||||
- For SPEC/PRODUCT, prefer updating existing statements over adding new ones
|
||||
(e.g. change "not supported in V1" to "supported via X" rather than adding
|
||||
a new section)
|
||||
|
||||
## Output
|
||||
|
||||
When the skill completes, report:
|
||||
|
||||
- How many commits were scanned
|
||||
- How many notable changes were found
|
||||
- How many doc edits were made (and to which files)
|
||||
- PR link (if edits were made)
|
||||
- Any follow-up items that need larger doc work
|
||||
85
.agents/skills/doc-maintenance/references/audit-checklist.md
Normal file
85
.agents/skills/doc-maintenance/references/audit-checklist.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Doc Maintenance Audit Checklist
|
||||
|
||||
Use this checklist when auditing each target document. For each item, compare
|
||||
against the change summary from git history.
|
||||
|
||||
## README.md
|
||||
|
||||
### Features table
|
||||
- [ ] Each feature card reflects a shipped capability
|
||||
- [ ] No feature cards for things that don't exist yet
|
||||
- [ ] No major shipped features missing from the table
|
||||
|
||||
### Roadmap
|
||||
- [ ] Nothing listed as "planned" or "coming soon" that already shipped
|
||||
- [ ] No removed/cancelled items still listed
|
||||
- [ ] Items reflect current priorities (cross-check with recent PRs)
|
||||
|
||||
### Quickstart
|
||||
- [ ] `npx paperclipai onboard` command is correct
|
||||
- [ ] Manual install steps are accurate (clone URL, commands)
|
||||
- [ ] Prerequisites (Node version, pnpm version) are current
|
||||
- [ ] Server URL and port are correct
|
||||
|
||||
### "What is Paperclip" section
|
||||
- [ ] High-level description is accurate
|
||||
- [ ] Step table (Define goal / Hire team / Approve and run) is correct
|
||||
|
||||
### "Works with" table
|
||||
- [ ] All supported adapters/runtimes are listed
|
||||
- [ ] No removed adapters still listed
|
||||
- [ ] Logos and labels match current adapter names
|
||||
|
||||
### "Paperclip is right for you if"
|
||||
- [ ] Use cases are still accurate
|
||||
- [ ] No claims about capabilities that don't exist
|
||||
|
||||
### "Why Paperclip is special"
|
||||
- [ ] Technical claims are accurate (atomic execution, governance, etc.)
|
||||
- [ ] No features listed that were removed or significantly changed
|
||||
|
||||
### FAQ
|
||||
- [ ] Answers are still correct
|
||||
- [ ] No references to removed features or outdated behavior
|
||||
|
||||
### Development section
|
||||
- [ ] Commands are accurate (`pnpm dev`, `pnpm build`, etc.)
|
||||
- [ ] Link to DEVELOPING.md is correct
|
||||
|
||||
## doc/SPEC.md
|
||||
|
||||
### Company Model
|
||||
- [ ] Fields match current schema
|
||||
- [ ] Governance model description is accurate
|
||||
|
||||
### Agent Model
|
||||
- [ ] Adapter types match what's actually supported
|
||||
- [ ] Agent configuration description is accurate
|
||||
- [ ] No features described as "not supported" or "not V1" that shipped
|
||||
|
||||
### Task Model
|
||||
- [ ] Task hierarchy description is accurate
|
||||
- [ ] Status values match current implementation
|
||||
|
||||
### Extensions / Plugins
|
||||
- [ ] If plugins are shipped, no "not in V1" or "future" language
|
||||
- [ ] Plugin model description matches implementation
|
||||
|
||||
### Open Questions
|
||||
- [ ] Resolved questions removed or updated
|
||||
- [ ] No "TBD" items that have been decided
|
||||
|
||||
## doc/PRODUCT.md
|
||||
|
||||
### Core Concepts
|
||||
- [ ] Company, Employees, Task Management descriptions accurate
|
||||
- [ ] Agent Execution modes described correctly
|
||||
- [ ] No missing major concepts
|
||||
|
||||
### Principles
|
||||
- [ ] Principles haven't been contradicted by shipped features
|
||||
- [ ] No principles referencing removed capabilities
|
||||
|
||||
### User Flow
|
||||
- [ ] Dream scenario still reflects actual onboarding
|
||||
- [ ] Steps are achievable with current features
|
||||
22
.agents/skills/doc-maintenance/references/section-map.md
Normal file
22
.agents/skills/doc-maintenance/references/section-map.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Section Map
|
||||
|
||||
Maps feature areas to specific document sections so the skill knows where to
|
||||
look when a feature ships or changes.
|
||||
|
||||
| Feature Area | README Section | SPEC Section | PRODUCT Section |
|
||||
|-------------|---------------|-------------|----------------|
|
||||
| Plugins / Extensions | Features table, Roadmap | Extensions, Agent Model | Core Concepts |
|
||||
| Adapters (new runtimes) | "Works with" table, FAQ | Agent Model, Agent Configuration | Employees & Agents, Agent Execution |
|
||||
| Governance / Approvals | Features table, "Why special" | Board Governance, Board Approval Gates | Principles |
|
||||
| Budget / Cost Control | Features table, "Why special" | Budget Delegation | Company (revenue & expenses) |
|
||||
| Task Management | Features table | Task Model | Task Management |
|
||||
| Org Chart / Hierarchy | Features table | Agent Model (reporting) | Employees & Agents |
|
||||
| Multi-Company | Features table, FAQ | Company Model | Company |
|
||||
| Heartbeats | Features table, FAQ | Agent Execution | Agent Execution |
|
||||
| CLI Commands | Development section | — | — |
|
||||
| Onboarding / Quickstart | Quickstart, FAQ | — | User Flow |
|
||||
| Skills / Skill Injection | "Why special" | — | — |
|
||||
| Company Templates | "Why special", Roadmap (ClipMart) | — | — |
|
||||
| Mobile / UI | Features table | — | — |
|
||||
| Project Archiving | — | — | — |
|
||||
| OpenClaw Integration | "Works with" table, FAQ | Agent Model | Agent Execution |
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -43,4 +43,5 @@ tmp/
|
||||
|
||||
# Playwright
|
||||
tests/e2e/test-results/
|
||||
tests/e2e/playwright-report/
|
||||
tests/e2e/playwright-report/
|
||||
.superset/
|
||||
@@ -89,6 +89,10 @@ docker compose -f docker-compose.quickstart.yml up --build
|
||||
|
||||
See `doc/DOCKER.md` for API key wiring (`OPENAI_API_KEY` / `ANTHROPIC_API_KEY`) and persistence details.
|
||||
|
||||
## Docker For Untrusted PR Review
|
||||
|
||||
For a separate review-oriented container that keeps `codex`/`claude` login state in Docker volumes and checks out PRs into an isolated scratch workspace, see `doc/UNTRUSTED-PR-REVIEW.md`.
|
||||
|
||||
## Database in Dev (Auto-Handled)
|
||||
|
||||
For local development, leave `DATABASE_URL` unset.
|
||||
|
||||
@@ -93,6 +93,12 @@ Notes:
|
||||
- Without API keys, the app still runs normally.
|
||||
- Adapter environment checks in Paperclip will surface missing auth/CLI prerequisites.
|
||||
|
||||
## Untrusted PR Review Container
|
||||
|
||||
If you want a separate Docker environment for reviewing untrusted pull requests with `codex` or `claude`, use the dedicated review workflow in `doc/UNTRUSTED-PR-REVIEW.md`.
|
||||
|
||||
That setup keeps CLI auth state in Docker volumes instead of your host home directory and uses a separate scratch workspace for PR checkouts and preview runs.
|
||||
|
||||
## Onboard Smoke Test (Ubuntu + npm only)
|
||||
|
||||
Use this when you want to mimic a fresh machine that only has Ubuntu + npm and verify:
|
||||
|
||||
135
doc/UNTRUSTED-PR-REVIEW.md
Normal file
135
doc/UNTRUSTED-PR-REVIEW.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# Untrusted PR Review In Docker
|
||||
|
||||
Use this workflow when you want Codex or Claude to inspect a pull request that you do not want touching your host machine directly.
|
||||
|
||||
This is intentionally separate from the normal Paperclip dev image.
|
||||
|
||||
## What this container isolates
|
||||
|
||||
- `codex` auth/session state in a Docker volume, not your host `~/.codex`
|
||||
- `claude` auth/session state in a Docker volume, not your host `~/.claude`
|
||||
- `gh` auth state in the same container-local home volume
|
||||
- review clones, worktrees, dependency installs, and local databases in a writable scratch volume under `/work`
|
||||
|
||||
By default this workflow does **not** mount your host repo checkout, your host home directory, or your SSH agent.
|
||||
|
||||
## Files
|
||||
|
||||
- `docker/untrusted-review/Dockerfile`
|
||||
- `docker-compose.untrusted-review.yml`
|
||||
- `review-checkout-pr` inside the container
|
||||
|
||||
## Build and start a shell
|
||||
|
||||
```sh
|
||||
docker compose -f docker-compose.untrusted-review.yml build
|
||||
docker compose -f docker-compose.untrusted-review.yml run --rm --service-ports review
|
||||
```
|
||||
|
||||
That opens an interactive shell in the review container with:
|
||||
|
||||
- Node + Corepack/pnpm
|
||||
- `codex`
|
||||
- `claude`
|
||||
- `gh`
|
||||
- `git`, `rg`, `fd`, `jq`
|
||||
|
||||
## First-time login inside the container
|
||||
|
||||
Run these once. The resulting login state persists in the `review-home` Docker volume.
|
||||
|
||||
```sh
|
||||
gh auth login
|
||||
codex login
|
||||
claude login
|
||||
```
|
||||
|
||||
If you prefer API-key auth instead of CLI login, pass keys through Compose env:
|
||||
|
||||
```sh
|
||||
OPENAI_API_KEY=... ANTHROPIC_API_KEY=... docker compose -f docker-compose.untrusted-review.yml run --rm review
|
||||
```
|
||||
|
||||
## Check out a PR safely
|
||||
|
||||
Inside the container:
|
||||
|
||||
```sh
|
||||
review-checkout-pr paperclipai/paperclip 432
|
||||
cd /work/checkouts/paperclipai-paperclip/pr-432
|
||||
```
|
||||
|
||||
What this does:
|
||||
|
||||
1. Creates or reuses a repo clone under `/work/repos/...`
|
||||
2. Fetches `pull/<pr>/head` from GitHub
|
||||
3. Creates a detached git worktree under `/work/checkouts/...`
|
||||
|
||||
The checkout lives entirely inside the container volume.
|
||||
|
||||
## Ask Codex or Claude to review it
|
||||
|
||||
Inside the PR checkout:
|
||||
|
||||
```sh
|
||||
codex
|
||||
```
|
||||
|
||||
Then give it a prompt like:
|
||||
|
||||
```text
|
||||
Review this PR as hostile input. Focus on security issues, data exfiltration paths, sandbox escapes, dangerous install/runtime scripts, auth changes, and subtle behavioral regressions. Do not modify files. Produce findings ordered by severity with file references.
|
||||
```
|
||||
|
||||
Or with Claude:
|
||||
|
||||
```sh
|
||||
claude
|
||||
```
|
||||
|
||||
## Preview the Paperclip app from the PR
|
||||
|
||||
Only do this when you intentionally want to execute the PR's code inside the container.
|
||||
|
||||
Inside the PR checkout:
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
HOST=0.0.0.0 pnpm dev
|
||||
```
|
||||
|
||||
Open from the host:
|
||||
|
||||
- `http://localhost:3100`
|
||||
|
||||
The Compose file also exposes Vite's default port:
|
||||
|
||||
- `http://localhost:5173`
|
||||
|
||||
Notes:
|
||||
|
||||
- `pnpm install` can run untrusted lifecycle scripts from the PR. That is why this happens inside the isolated container instead of on your host.
|
||||
- If you only want static inspection, do not run install/dev commands.
|
||||
- Paperclip's embedded PostgreSQL and local storage stay inside the container home volume via `PAPERCLIP_HOME=/home/reviewer/.paperclip-review`.
|
||||
|
||||
## Reset state
|
||||
|
||||
Remove the review container volumes when you want a clean environment:
|
||||
|
||||
```sh
|
||||
docker compose -f docker-compose.untrusted-review.yml down -v
|
||||
```
|
||||
|
||||
That deletes:
|
||||
|
||||
- Codex/Claude/GitHub login state stored in `review-home`
|
||||
- cloned repos, worktrees, installs, and scratch data stored in `review-work`
|
||||
|
||||
## Security limits
|
||||
|
||||
This is a useful isolation boundary, but it is still Docker, not a full VM.
|
||||
|
||||
- A reviewed PR can still access the container's network unless you disable it.
|
||||
- Any secrets you pass into the container are available to code you execute inside it.
|
||||
- Do not mount your host repo, host home, `.ssh`, or Docker socket unless you are intentionally weakening the boundary.
|
||||
- If you need a stronger boundary than this, use a disposable VM instead of Docker.
|
||||
33
docker-compose.untrusted-review.yml
Normal file
33
docker-compose.untrusted-review.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
services:
|
||||
review:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/untrusted-review/Dockerfile
|
||||
init: true
|
||||
tty: true
|
||||
stdin_open: true
|
||||
working_dir: /work
|
||||
environment:
|
||||
HOME: "/home/reviewer"
|
||||
CODEX_HOME: "/home/reviewer/.codex"
|
||||
CLAUDE_HOME: "/home/reviewer/.claude"
|
||||
PAPERCLIP_HOME: "/home/reviewer/.paperclip-review"
|
||||
OPENAI_API_KEY: "${OPENAI_API_KEY:-}"
|
||||
ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY:-}"
|
||||
GITHUB_TOKEN: "${GITHUB_TOKEN:-}"
|
||||
ports:
|
||||
- "${REVIEW_PAPERCLIP_PORT:-3100}:3100"
|
||||
- "${REVIEW_VITE_PORT:-5173}:5173"
|
||||
volumes:
|
||||
- review-home:/home/reviewer
|
||||
- review-work:/work
|
||||
cap_drop:
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
tmpfs:
|
||||
- /tmp:mode=1777,size=1g
|
||||
|
||||
volumes:
|
||||
review-home:
|
||||
review-work:
|
||||
44
docker/untrusted-review/Dockerfile
Normal file
44
docker/untrusted-review/Dockerfile
Normal file
@@ -0,0 +1,44 @@
|
||||
FROM node:lts-trixie-slim
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
bash \
|
||||
ca-certificates \
|
||||
curl \
|
||||
fd-find \
|
||||
gh \
|
||||
git \
|
||||
jq \
|
||||
less \
|
||||
openssh-client \
|
||||
procps \
|
||||
ripgrep \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN ln -sf /usr/bin/fdfind /usr/local/bin/fd
|
||||
|
||||
RUN corepack enable \
|
||||
&& npm install --global --omit=dev @anthropic-ai/claude-code@latest @openai/codex@latest
|
||||
|
||||
RUN useradd --create-home --shell /bin/bash reviewer
|
||||
|
||||
ENV HOME=/home/reviewer \
|
||||
CODEX_HOME=/home/reviewer/.codex \
|
||||
CLAUDE_HOME=/home/reviewer/.claude \
|
||||
PAPERCLIP_HOME=/home/reviewer/.paperclip-review \
|
||||
PNPM_HOME=/home/reviewer/.local/share/pnpm \
|
||||
PATH=/home/reviewer/.local/share/pnpm:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
COPY --chown=reviewer:reviewer docker/untrusted-review/bin/review-checkout-pr /usr/local/bin/review-checkout-pr
|
||||
|
||||
RUN chmod +x /usr/local/bin/review-checkout-pr \
|
||||
&& mkdir -p /work \
|
||||
&& chown -R reviewer:reviewer /work
|
||||
|
||||
USER reviewer
|
||||
|
||||
EXPOSE 3100 5173
|
||||
|
||||
CMD ["bash", "-l"]
|
||||
65
docker/untrusted-review/bin/review-checkout-pr
Normal file
65
docker/untrusted-review/bin/review-checkout-pr
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: review-checkout-pr <owner/repo|github-url> <pr-number> [checkout-dir]
|
||||
|
||||
Examples:
|
||||
review-checkout-pr paperclipai/paperclip 432
|
||||
review-checkout-pr https://github.com/paperclipai/paperclip.git 432
|
||||
EOF
|
||||
}
|
||||
|
||||
if [[ $# -lt 2 || $# -gt 3 ]]; then
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
normalize_repo_slug() {
|
||||
local raw="$1"
|
||||
raw="${raw#git@github.com:}"
|
||||
raw="${raw#ssh://git@github.com/}"
|
||||
raw="${raw#https://github.com/}"
|
||||
raw="${raw#http://github.com/}"
|
||||
raw="${raw%.git}"
|
||||
printf '%s\n' "${raw#/}"
|
||||
}
|
||||
|
||||
repo_slug="$(normalize_repo_slug "$1")"
|
||||
pr_number="$2"
|
||||
|
||||
if [[ ! "$repo_slug" =~ ^[^/]+/[^/]+$ ]]; then
|
||||
echo "Expected GitHub repo slug like owner/repo or a GitHub repo URL, got: $1" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "$pr_number" =~ ^[0-9]+$ ]]; then
|
||||
echo "PR number must be numeric, got: $pr_number" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
repo_key="${repo_slug//\//-}"
|
||||
mirror_dir="/work/repos/${repo_key}"
|
||||
checkout_dir="${3:-/work/checkouts/${repo_key}/pr-${pr_number}}"
|
||||
pr_ref="refs/remotes/origin/pr/${pr_number}"
|
||||
|
||||
mkdir -p "$(dirname "$mirror_dir")" "$(dirname "$checkout_dir")"
|
||||
|
||||
if [[ ! -d "$mirror_dir/.git" ]]; then
|
||||
if command -v gh >/dev/null 2>&1; then
|
||||
gh repo clone "$repo_slug" "$mirror_dir" -- --filter=blob:none
|
||||
else
|
||||
git clone --filter=blob:none "https://github.com/${repo_slug}.git" "$mirror_dir"
|
||||
fi
|
||||
fi
|
||||
|
||||
git -C "$mirror_dir" fetch --force origin "pull/${pr_number}/head:${pr_ref}"
|
||||
|
||||
if [[ -e "$checkout_dir" ]]; then
|
||||
printf '%s\n' "$checkout_dir"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git -C "$mirror_dir" worktree add --detach "$checkout_dir" "$pr_ref" >/dev/null
|
||||
printf '%s\n' "$checkout_dir"
|
||||
@@ -41,7 +41,6 @@
|
||||
"@paperclipai/adapter-openclaw-gateway": "workspace:*",
|
||||
"@paperclipai/adapter-opencode-local": "workspace:*",
|
||||
"@paperclipai/adapter-pi-local": "workspace:*",
|
||||
"@paperclipai/adapter-openclaw-gateway": "workspace:*",
|
||||
"hermes-paperclip-adapter": "0.1.1",
|
||||
"@paperclipai/adapter-utils": "workspace:*",
|
||||
"@paperclipai/db": "workspace:*",
|
||||
|
||||
@@ -75,11 +75,15 @@ export function CommandPalette() {
|
||||
enabled: !!selectedCompanyId && open,
|
||||
});
|
||||
|
||||
const { data: projects = [] } = useQuery({
|
||||
const { data: allProjects = [] } = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId!),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!),
|
||||
enabled: !!selectedCompanyId && open,
|
||||
});
|
||||
const projects = useMemo(
|
||||
() => allProjects.filter((p) => !p.archivedAt),
|
||||
[allProjects],
|
||||
);
|
||||
|
||||
function go(path: string) {
|
||||
setOpen(false);
|
||||
|
||||
@@ -131,8 +131,12 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp
|
||||
queryFn: () => projectsApi.list(companyId!),
|
||||
enabled: !!companyId,
|
||||
});
|
||||
const activeProjects = useMemo(
|
||||
() => (projects ?? []).filter((p) => !p.archivedAt || p.id === issue.projectId),
|
||||
[projects, issue.projectId],
|
||||
);
|
||||
const { orderedProjects } = useProjectOrder({
|
||||
projects: projects ?? [],
|
||||
projects: activeProjects,
|
||||
companyId,
|
||||
userId: currentUserId,
|
||||
});
|
||||
|
||||
@@ -117,7 +117,7 @@ export function MarkdownBody({ children, className }: MarkdownBodyProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"paperclip-markdown prose prose-sm max-w-none break-words overflow-hidden prose-pre:whitespace-pre-wrap prose-pre:break-words prose-code:break-all",
|
||||
"paperclip-markdown prose prose-sm max-w-none break-words overflow-hidden",
|
||||
theme === "dark" && "prose-invert",
|
||||
className,
|
||||
)}
|
||||
|
||||
@@ -288,8 +288,12 @@ export function NewIssueDialog() {
|
||||
queryFn: () => authApi.getSession(),
|
||||
});
|
||||
const currentUserId = session?.user?.id ?? session?.session?.userId ?? null;
|
||||
const activeProjects = useMemo(
|
||||
() => (projects ?? []).filter((p) => !p.archivedAt),
|
||||
[projects],
|
||||
);
|
||||
const { orderedProjects } = useProjectOrder({
|
||||
projects: projects ?? [],
|
||||
projects: activeProjects,
|
||||
companyId: effectiveCompanyId,
|
||||
userId: currentUserId,
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Separator } from "@/components/ui/separator";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { AlertCircle, Check, ExternalLink, Github, Loader2, Plus, Trash2, X } from "lucide-react";
|
||||
import { AlertCircle, Archive, ArchiveRestore, Check, ExternalLink, Github, Loader2, Plus, Trash2, X } from "lucide-react";
|
||||
import { ChoosePathButton } from "./PathInstructionsModal";
|
||||
import { DraftInput } from "./agent-config-primitives";
|
||||
import { InlineEditor } from "./InlineEditor";
|
||||
@@ -34,6 +34,8 @@ interface ProjectPropertiesProps {
|
||||
onUpdate?: (data: Record<string, unknown>) => void;
|
||||
onFieldUpdate?: (field: ProjectConfigFieldKey, data: Record<string, unknown>) => void;
|
||||
getFieldSaveState?: (field: ProjectConfigFieldKey) => ProjectFieldSaveState;
|
||||
onArchive?: (archived: boolean) => void;
|
||||
archivePending?: boolean;
|
||||
}
|
||||
|
||||
export type ProjectFieldSaveState = "idle" | "saving" | "saved" | "error";
|
||||
@@ -152,7 +154,7 @@ function ProjectStatusPicker({ status, onChange }: { status: string; onChange: (
|
||||
);
|
||||
}
|
||||
|
||||
export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSaveState }: ProjectPropertiesProps) {
|
||||
export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSaveState, onArchive, archivePending }: ProjectPropertiesProps) {
|
||||
const { selectedCompanyId } = useCompany();
|
||||
const queryClient = useQueryClient();
|
||||
const [goalOpen, setGoalOpen] = useState(false);
|
||||
@@ -954,6 +956,45 @@ export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSa
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
{onArchive && (
|
||||
<>
|
||||
<Separator className="my-4" />
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="text-xs font-medium text-destructive uppercase tracking-wide">
|
||||
Danger Zone
|
||||
</div>
|
||||
<div className="space-y-3 rounded-md border border-destructive/40 bg-destructive/5 px-4 py-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{project.archivedAt
|
||||
? "Unarchive this project to restore it in the sidebar and project selectors."
|
||||
: "Archive this project to hide it from the sidebar and project selectors."}
|
||||
</p>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
disabled={archivePending}
|
||||
onClick={() => {
|
||||
const action = project.archivedAt ? "Unarchive" : "Archive";
|
||||
const confirmed = window.confirm(
|
||||
`${action} project "${project.name}"?`,
|
||||
);
|
||||
if (!confirmed) return;
|
||||
onArchive(!project.archivedAt);
|
||||
}}
|
||||
>
|
||||
{archivePending ? (
|
||||
<><Loader2 className="h-3 w-3 animate-spin mr-1" />{project.archivedAt ? "Unarchiving..." : "Archiving..."}</>
|
||||
) : project.archivedAt ? (
|
||||
<><ArchiveRestore className="h-3 w-3 mr-1" />Unarchive project</>
|
||||
) : (
|
||||
<><Archive className="h-3 w-3 mr-1" />Archive project</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
119
ui/src/index.css
119
ui/src/index.css
@@ -178,12 +178,13 @@
|
||||
background: oklch(0.5 0 0);
|
||||
}
|
||||
|
||||
/* Auto-hide scrollbar: fully transparent by default, visible on container hover */
|
||||
/* Auto-hide scrollbar: fully invisible by default, visible on container hover */
|
||||
.scrollbar-auto-hide::-webkit-scrollbar-track {
|
||||
background: transparent !important;
|
||||
}
|
||||
.scrollbar-auto-hide::-webkit-scrollbar-thumb {
|
||||
background: transparent !important;
|
||||
transition: background 150ms ease;
|
||||
}
|
||||
.scrollbar-auto-hide:hover::-webkit-scrollbar-track {
|
||||
background: oklch(0.205 0 0) !important;
|
||||
@@ -411,30 +412,118 @@
|
||||
|
||||
.paperclip-mdxeditor-content code {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 0.84em;
|
||||
font-size: 0.78em;
|
||||
}
|
||||
|
||||
.paperclip-mdxeditor-content pre {
|
||||
margin: 0.5rem 0;
|
||||
padding: 0.45rem 0.55rem;
|
||||
border: 1px solid var(--border);
|
||||
margin: 0.4rem 0;
|
||||
padding: 0;
|
||||
border: 1px solid color-mix(in oklab, var(--foreground) 12%, transparent);
|
||||
border-radius: calc(var(--radius) - 3px);
|
||||
background: color-mix(in oklab, var(--accent) 50%, transparent);
|
||||
background: #1e1e2e;
|
||||
color: #cdd6f4;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* Rendered markdown code blocks & inline code (prose/MarkdownBody context).
|
||||
Matches the editor theme so rendered code looks consistent. */
|
||||
.prose pre {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: calc(var(--radius) - 3px);
|
||||
background-color: color-mix(in oklab, var(--accent) 50%, transparent);
|
||||
color: var(--foreground);
|
||||
/* Dark theme for CodeMirror code blocks inside the MDXEditor.
|
||||
Overrides the default cm6-theme-basic-light that MDXEditor bundles. */
|
||||
.paperclip-mdxeditor .cm-editor {
|
||||
background-color: #1e1e2e !important;
|
||||
color: #cdd6f4 !important;
|
||||
font-size: 0.78em;
|
||||
}
|
||||
|
||||
.prose code {
|
||||
.paperclip-mdxeditor .cm-gutters {
|
||||
background-color: #181825 !important;
|
||||
color: #585b70 !important;
|
||||
border-right: 1px solid #313244 !important;
|
||||
}
|
||||
|
||||
.paperclip-mdxeditor .cm-activeLineGutter {
|
||||
background-color: #1e1e2e !important;
|
||||
}
|
||||
|
||||
.paperclip-mdxeditor .cm-activeLine {
|
||||
background-color: color-mix(in oklab, #cdd6f4 5%, transparent) !important;
|
||||
}
|
||||
|
||||
.paperclip-mdxeditor .cm-cursor,
|
||||
.paperclip-mdxeditor .cm-dropCursor {
|
||||
border-left-color: #cdd6f4 !important;
|
||||
}
|
||||
|
||||
.paperclip-mdxeditor .cm-selectionBackground {
|
||||
background-color: color-mix(in oklab, #89b4fa 25%, transparent) !important;
|
||||
}
|
||||
|
||||
.paperclip-mdxeditor .cm-focused .cm-selectionBackground {
|
||||
background-color: color-mix(in oklab, #89b4fa 30%, transparent) !important;
|
||||
}
|
||||
|
||||
.paperclip-mdxeditor .cm-content {
|
||||
caret-color: #cdd6f4;
|
||||
}
|
||||
|
||||
/* MDXEditor code block language selector – show on hover only */
|
||||
.paperclip-mdxeditor-content [class*="_codeMirrorWrapper_"] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.paperclip-mdxeditor-content [class*="_codeMirrorToolbar_"],
|
||||
.paperclip-mdxeditor-content [class*="_codeBlockToolbar_"] {
|
||||
position: absolute;
|
||||
top: 0.25rem;
|
||||
right: 0.25rem;
|
||||
z-index: 2;
|
||||
opacity: 0;
|
||||
transition: opacity 150ms ease;
|
||||
}
|
||||
|
||||
.paperclip-mdxeditor-content [class*="_codeMirrorToolbar_"] select,
|
||||
.paperclip-mdxeditor-content [class*="_codeBlockToolbar_"] select {
|
||||
background-color: #313244;
|
||||
color: #cdd6f4;
|
||||
border-color: #45475a;
|
||||
}
|
||||
|
||||
.paperclip-mdxeditor-content [class*="_codeMirrorWrapper_"]:hover [class*="_codeMirrorToolbar_"],
|
||||
.paperclip-mdxeditor-content [class*="_codeMirrorWrapper_"]:hover [class*="_codeBlockToolbar_"],
|
||||
.paperclip-mdxeditor-content [class*="_codeMirrorWrapper_"]:focus-within [class*="_codeMirrorToolbar_"],
|
||||
.paperclip-mdxeditor-content [class*="_codeMirrorWrapper_"]:focus-within [class*="_codeBlockToolbar_"] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Rendered markdown code blocks & inline code (prose/MarkdownBody context).
|
||||
Dark theme code blocks with compact sizing.
|
||||
Override prose CSS variables so prose-invert can't revert to defaults. */
|
||||
.paperclip-markdown {
|
||||
--tw-prose-pre-bg: #1e1e2e;
|
||||
--tw-prose-pre-code: #cdd6f4;
|
||||
--tw-prose-invert-pre-bg: #1e1e2e;
|
||||
--tw-prose-invert-pre-code: #cdd6f4;
|
||||
}
|
||||
|
||||
.paperclip-markdown pre {
|
||||
border: 1px solid color-mix(in oklab, var(--foreground) 12%, transparent) !important;
|
||||
border-radius: calc(var(--radius) - 3px) !important;
|
||||
background-color: #1e1e2e !important;
|
||||
color: #cdd6f4 !important;
|
||||
padding: 0.5rem 0.65rem !important;
|
||||
margin: 0.4rem 0 !important;
|
||||
font-size: 0.78em !important;
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.paperclip-markdown code {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 0.84em;
|
||||
font-size: 0.78em;
|
||||
}
|
||||
|
||||
.paperclip-markdown pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
background: none;
|
||||
}
|
||||
|
||||
/* Remove backtick pseudo-elements from inline code (prose default adds them) */
|
||||
|
||||
@@ -269,7 +269,7 @@ export function OrgChart() {
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="w-full h-[calc(100vh-4rem)] overflow-hidden relative bg-muted/20 border border-border rounded-lg"
|
||||
className="w-full h-[calc(100dvh-6rem)] overflow-hidden relative bg-muted/20 border border-border rounded-lg"
|
||||
style={{ cursor: dragging ? "grabbing" : "grab" }}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
|
||||
@@ -274,6 +274,21 @@ export function ProjectDetail() {
|
||||
onSuccess: invalidateProject,
|
||||
});
|
||||
|
||||
const archiveProject = useMutation({
|
||||
mutationFn: (archived: boolean) =>
|
||||
projectsApi.update(
|
||||
projectLookupRef,
|
||||
{ archivedAt: archived ? new Date().toISOString() : null },
|
||||
resolvedCompanyId ?? lookupCompanyId,
|
||||
),
|
||||
onSuccess: (_, archived) => {
|
||||
invalidateProject();
|
||||
if (archived) {
|
||||
navigate("/projects");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const uploadImage = useMutation({
|
||||
mutationFn: async (file: File) => {
|
||||
if (!resolvedCompanyId) throw new Error("No company selected");
|
||||
@@ -476,6 +491,8 @@ export function ProjectDetail() {
|
||||
onUpdate={(data) => updateProject.mutate(data)}
|
||||
onFieldUpdate={updateProjectField}
|
||||
getFieldSaveState={(field) => fieldSaveStates[field] ?? "idle"}
|
||||
onArchive={(archived) => archiveProject.mutate(archived)}
|
||||
archivePending={archiveProject.isPending}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { useCompany } from "../context/CompanyContext";
|
||||
@@ -22,11 +22,15 @@ export function Projects() {
|
||||
setBreadcrumbs([{ label: "Projects" }]);
|
||||
}, [setBreadcrumbs]);
|
||||
|
||||
const { data: projects, isLoading, error } = useQuery({
|
||||
const { data: allProjects, isLoading, error } = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId!),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const projects = useMemo(
|
||||
() => (allProjects ?? []).filter((p) => !p.archivedAt),
|
||||
[allProjects],
|
||||
);
|
||||
|
||||
if (!selectedCompanyId) {
|
||||
return <EmptyState icon={Hexagon} message="Select a company to view projects." />;
|
||||
@@ -47,7 +51,7 @@ export function Projects() {
|
||||
|
||||
{error && <p className="text-sm text-destructive">{error.message}</p>}
|
||||
|
||||
{projects && projects.length === 0 && (
|
||||
{!isLoading && projects.length === 0 && (
|
||||
<EmptyState
|
||||
icon={Hexagon}
|
||||
message="No projects yet."
|
||||
@@ -56,7 +60,7 @@ export function Projects() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{projects && projects.length > 0 && (
|
||||
{projects.length > 0 && (
|
||||
<div className="border border-border">
|
||||
{projects.map((project) => (
|
||||
<EntityRow
|
||||
|
||||
Reference in New Issue
Block a user