Files
919f0ade99 Portal (#6391)
# Description of Changes

## What & why

This PR introduces the **Stirling developer portal** — a new
control-plane frontend that sits alongside the existing PDF editor —
plus the shared design system and workspace structure needed to host
both apps in one frontend.

The portal is the parent product surface: where users connect sources,
compose pipelines, wire agents, and manage usage / billing /
infrastructure, with the PDF editor as one capability inside it. This PR
lays the **foundation** — workspace reshape, design system, app shell,
navigation, and a mock-driven home — rather than wiring real backends
(those surfaces are placeholders for follow-up phases).

## What's in this PR

**1. Frontend repo reshape (`frontend/src/` → `frontend/editor/`)**
The existing editor app moved under `frontend/editor/`, so `editor`,
`portal`, and `shared` are siblings in one workspace. All references
were updated accordingly: `LICENSE`, `.dockerignore`, `.gitignore`,
build/sign shell scripts, the GH language-check script, the Taskfile,
and Docker config. **No editor source logic changed — path references
only.**

**2. New shared design system (`frontend/shared/`)**
- **Design tokens** in `tokens.css` as the single runtime source of
truth (light/dark, category accents, gradients). `tokens.ts` now holds
only the `Tier` type — the old JS palette mirror was removed (nothing
consumed it and it had drifted).
- ~30 framework-light **components** (Card, Button, Input, Select, Tabs,
Modal, Drawer, Toast, MetricCard, StatusBadge, Skeleton, EmptyState, …)
with Storybook stories.
- **Typed data catalogues**: `endpoints.ts` (10 verticals / 64
endpoints) and `ops.ts`.

**3. New developer portal app (`frontend/portal/`)**
- App shell: `Header`, `Sidebar`, `AssistantPanel`, search modal,
notifications, tier switcher, theme toggle, MSW toggle.
- **Tier-aware** home (free / pay-as-you-go / enterprise): KPI strip,
30-day usage chart, onboarding checklist, quick actions, recent
activity, region health, product grid, and a curated **"Popular use
cases"** teaser.
- **Documents** view hosting the full, tab-filterable endpoint
catalogue.
- Placeholder views for Sources / Pipelines / Agents / Editor /
Infrastructure / Usage & Billing / Developer Docs / Settings (follow-up
phases).
- **MSW-mocked** API layer: `api/*` issues real `fetch`, intercepted by
mocks in dev/Storybook; pointing at a real backend is just a matter of
not registering MSW. `react-router` URLs; Tier / View / UI contexts.

**4. Tooling & guardrails**
- ESLint extended to `portal` + `shared`, with **layering-boundary
rules**: `shared/` may depend only on third-party packages and itself
(no `@app` / `@portal` / `@core` / `@proprietary` / Tauri), so it stays
cleanly extractable into a standalone package later.
- `dpdm` circular-dependency check now walks editor + portal + shared
(the old glob matched only 2 files).
- New **devDependencies only** — Storybook (+ a11y/docs/themes addons),
MSW. No runtime dependencies added.
- New tasks: `frontend:dev:portal`, `frontend:build:portal`.

## Testing done locally

- `tsc` for both `portal` and `shared` projects — clean
- `eslint --max-warnings=0` across the whole frontend — clean
- `dpdm` circular-dependency check — no cycles
- Editor builds clean: `vite build editor --mode core` (✓ built, only
the pre-existing >500 kB chunk-size advisory)
- Editor runs in dev (core mode) with **zero console errors**; portal
runs in dev across all three tiers

## Notes for reviewers

- The change is overwhelmingly **additive**: `shared/` and `portal/` are
brand-new; the existing editor is path-reference changes only.
- The portal is intentionally **mock-driven** at this stage — real
backends and the remaining views land in follow-up phases.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [x] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-02 16:08:24 +00:00

1309 lines
34 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* The Stirling operation library.
*
* Three layers, ported faithfully from the prototype:
*
* 1. PIPELINE_OPS — the canonical pipeline-stage taxonomy. Ops are grouped
* by `OpKind` (ingest / validate / modify / secure / store / alert) which
* drives accent colours in chip rendering.
*
* 2. LIBRARY_OPS — the broader catalogue surfaced in the composer's
* Operations picker. Includes everything in PIPELINE_OPS plus the wider
* PDF tool set (formatting, extraction, AI-powered ops, schema-aware
* ops). Each library op also carries an `OpCategory` for finer-grained
* grouping in the picker UI.
*
* 3. PIPELINE_AGENTS — one-click bundles of related ops (PII Sweep, Trust &
* Verify, Compliance Pack, Format Prep). Selecting an agent expands its
* `ops` list into the chip row.
*
* SOURCE_OPTIONS and DESTINATION_OPTIONS sit alongside as the structural
* rails — every pipeline has exactly one source and one destination.
*/
/** The pipeline-stage taxonomy a chip-row uses for accent colours. */
export type OpKind =
| "ingest"
| "validate"
| "modify"
| "secure"
| "store"
| "alert";
/** Finer-grained groupings shown in the Operations picker. */
export type OpCategory =
| "Document Security"
| "Validation"
| "Classification"
| "Document Review"
| "Signing"
| "Page Formatting"
| "Extraction"
| "Removal"
| "Automation"
| "Advanced Formatting"
| "Developer Tools";
export interface PipelineOp {
id: string;
label: string;
icon: string;
desc: string;
kind: OpKind;
/** Ship in the default chip row when this op's stage is on. */
defaultOn?: boolean;
/** Only configurable in the pipeline composer (not as an ad-hoc op). */
pipelineOnly?: boolean;
}
export interface LibraryOp extends Omit<
PipelineOp,
"defaultOn" | "pipelineOnly"
> {
category: OpCategory;
provider?: "claude" | "stirling";
}
export interface PipelineAgent {
id: string;
label: string;
icon: string;
desc: string;
/** Op IDs from PIPELINE_OPS that this agent expands into. */
ops: string[];
}
export interface SourceOption {
id: "upload" | "webhook" | "s3" | "email" | "scheduled";
label: string;
icon: string;
desc: string;
}
export interface DestinationOption {
id: "vault" | "s3" | "webhook" | "pipeline" | "database" | "sftp";
label: string;
icon: string;
desc: string;
}
/* ──────────────────────────────────────────────────────────────────────── */
/* PIPELINE_OPS — canonical stages */
/* ──────────────────────────────────────────────────────────────────────── */
export const PIPELINE_OPS: Record<OpKind, PipelineOp[]> = {
ingest: [
{
id: "ocr",
label: "OCR",
icon: "eye",
kind: "ingest",
desc: "Text-recognize scanned or image-based pages",
},
{
id: "parse",
label: "Parse",
icon: "layout",
kind: "ingest",
desc: "Reconstruct reading order and layout structure",
},
{
id: "classify",
label: "Classify",
icon: "layers",
kind: "ingest",
desc: "Identify the document type and return a confidence score",
},
{
id: "bundle-split",
label: "Bundle split",
icon: "grid",
kind: "ingest",
desc: "Split a multi-document upload into component documents",
},
{
id: "extract",
label: "Extract",
icon: "sparkles",
kind: "ingest",
desc: "Pull structured fields from the document into a typed schema",
},
],
validate: [
{
id: "validate",
label: "Schema validate",
icon: "shield",
kind: "validate",
desc: "Check required fields, business rules, and coverage gaps against the typed schema",
},
{
id: "authenticity",
label: "Authenticity",
icon: "check",
kind: "validate",
desc: "Verify the document is genuine (signature, issuer, watermark checks)",
},
{
id: "tamper-check",
label: "Tamper check",
icon: "alertTriangle",
kind: "validate",
desc: "Detect modifications since signing or last-known-good state",
},
{
id: "source-auth",
label: "Source auth",
icon: "lock",
kind: "validate",
desc: "Confirm the document came from an authenticated source",
},
{
id: "counterparty-match",
label: "Counterparty match",
icon: "userPlus",
kind: "validate",
desc: "Match the document counterparty against expected identity",
},
{
id: "confidence-check",
label: "Confidence bounds",
icon: "activity",
kind: "validate",
desc: "Gate downstream ops on extraction-confidence thresholds",
},
],
modify: [
{
id: "merge",
label: "Merge",
icon: "layers",
kind: "modify",
desc: "Combine multiple PDFs into a single document",
},
{
id: "split",
label: "Split",
icon: "grid",
kind: "modify",
desc: "Split a PDF into pages, sections, or by document boundary",
},
{
id: "convert",
label: "Convert",
icon: "fileText",
kind: "modify",
desc: "Convert between PDF, Word, Excel, image, HTML, Markdown",
},
{
id: "compress",
label: "Compress",
icon: "package",
kind: "modify",
desc: "Reduce file size while preserving fidelity",
},
{
id: "rotate",
label: "Rotate",
icon: "arrowRight",
kind: "modify",
desc: "Rotate pages or correct page orientation",
},
{
id: "crop",
label: "Crop",
icon: "layout",
kind: "modify",
desc: "Crop pages to a region or trim margins",
},
],
secure: [
{
id: "redact",
label: "Redact PII",
icon: "eye",
kind: "secure",
defaultOn: true,
desc: "Remove or mask PII before the document is stored or released downstream",
},
{
id: "pii-enforce",
label: "PII/PHI enforcement",
icon: "shield",
kind: "secure",
desc: "Policy check that outputs do not leak protected data beyond declared scope",
},
{
id: "watermark",
label: "Confidentiality mark",
icon: "penTool",
kind: "secure",
desc: "Stamp a visible confidentiality / classification watermark onto pages",
},
{
id: "attribution-watermark",
label: "Attribution watermark",
icon: "penTool",
kind: "secure",
desc: "Invisible per-recipient watermarking for leak tracing",
},
{
id: "flatten",
label: "Flatten + lock",
icon: "layout",
kind: "secure",
desc: "Flatten forms and annotations to harden the document against tampering",
},
{
id: "sign-output",
label: "Signed outputs",
icon: "penTool",
kind: "secure",
desc: "Tamper-evident signatures covering the artifact and run metadata",
},
{
id: "encrypt-rest",
label: "Encryption at rest",
icon: "lock",
kind: "secure",
defaultOn: true,
desc: "AES-256 on stored artifacts. Stirling-managed, customer KMS, or BYOK",
},
{
id: "retention",
label: "Retention policy",
icon: "fileText",
kind: "secure",
defaultOn: true,
pipelineOnly: true,
desc: "How long Stirling retains the artifact, run record, and audit trail",
},
{
id: "residency",
label: "Regional residency",
icon: "globe",
kind: "secure",
pipelineOnly: true,
desc: "Where the artifact is stored and processed. Region-pin or air-gap",
},
{
id: "access-policy",
label: "Access policy",
icon: "key",
kind: "secure",
defaultOn: true,
pipelineOnly: true,
desc: "Who can fetch the sealed artifact (signed URL, IdP-gated, public)",
},
],
store: [
{
id: "store-primary",
label: "Primary store",
icon: "fileText",
kind: "store",
defaultOn: true,
pipelineOnly: true,
desc: "Write the sealed artifact and its run record into the Documents centralized view",
},
{
id: "mirror-bucket",
label: "Mirror to bucket",
icon: "globe",
kind: "store",
pipelineOnly: true,
desc: "Copy the secured artifact to the customers S3 / GCS / Azure Blob for compliance archival",
},
{
id: "mirror-archive",
label: "Compliance archive",
icon: "lock",
kind: "store",
pipelineOnly: true,
desc: "Mirror to SharePoint / M365 Records / WORM-locked archive for regulated retention",
},
{
id: "emit-manifest",
label: "Processing manifest",
icon: "penTool",
kind: "store",
defaultOn: true,
pipelineOnly: true,
desc: "Emit a signed manifest describing every stage the document passed through",
},
],
alert: [
{
id: "review",
label: "Human review",
icon: "userPlus",
kind: "alert",
desc: "Route the document to the review queue when rules fail",
},
{
id: "flag",
label: "Flag",
icon: "alertTriangle",
kind: "alert",
desc: "Raise an in-app flag on low-confidence or out-of-policy docs",
},
{
id: "notify",
label: "Notify",
icon: "bell",
kind: "alert",
desc: "Fire a webhook or email when the pipeline finishes or trips a rule",
},
],
};
/** Flat lookup across every stage bucket. */
export const PIPELINE_OPS_INDEX: Record<string, PipelineOp> = (() => {
const idx: Record<string, PipelineOp> = {};
for (const kind of Object.keys(PIPELINE_OPS) as OpKind[]) {
for (const op of PIPELINE_OPS[kind]) idx[op.id] = op;
}
return idx;
})();
export const lookupOp = (id: string): PipelineOp | null =>
PIPELINE_OPS_INDEX[id] ?? null;
/* ──────────────────────────────────────────────────────────────────────── */
/* PIPELINE_AGENTS — one-click op bundles */
/* ──────────────────────────────────────────────────────────────────────── */
export const PIPELINE_AGENTS: readonly PipelineAgent[] = [
{
id: "agent-pii-sweep",
label: "PII Sweep",
icon: "shield",
desc: "Redact PII categories + encrypt the sealed artifact",
ops: ["redact", "encrypt-rest"],
},
{
id: "agent-trust-verify",
label: "Trust & Verify",
icon: "check",
desc: "Authenticity, tamper, and confidence checks before downstream",
ops: ["authenticity", "tamper-check", "confidence-check"],
},
{
id: "agent-compliance-pack",
label: "Compliance Pack",
icon: "lock",
desc: "Redact + watermark + sign + retention policy",
ops: ["redact", "watermark", "sign-output", "retention"],
},
{
id: "agent-format-prep",
label: "Format Prep",
icon: "package",
desc: "Compress and flatten before downstream consumption",
ops: ["compress", "flatten"],
},
];
export const lookupAgent = (id: string): PipelineAgent | null =>
PIPELINE_AGENTS.find((a) => a.id === id) ?? null;
/* ──────────────────────────────────────────────────────────────────────── */
/* OP_CATEGORIES — picker section metadata */
/* ──────────────────────────────────────────────────────────────────────── */
export interface OpCategoryMeta {
name: OpCategory;
color: string;
blurb: string;
}
export const OP_CATEGORIES: readonly OpCategoryMeta[] = [
{
name: "Document Security",
color: "var(--color-red)",
blurb: "PII, encryption, watermarks, policy seal",
},
{
name: "Validation",
color: "var(--color-blue)",
blurb: "Schema checks, trust gates, filters",
},
{
name: "Classification",
color: "var(--color-purple)",
blurb: "Type inference, routing, boundary detection",
},
{
name: "Document Review",
color: "var(--color-cat-compliance)",
blurb: "Inspection, AI review, certification",
},
{
name: "Signing",
color: "var(--color-green)",
blurb: "Digital signatures, cert sign / remove",
},
{
name: "Page Formatting",
color: "var(--color-cat-energy)",
blurb: "Rotate, crop, layout, page-level edits",
},
{
name: "Extraction",
color: "var(--color-cat-extraction)",
blurb: "OCR, format conversion, content pull",
},
{
name: "Removal",
color: "var(--color-amber)",
blurb: "Drop pages, blanks, images, partition splits",
},
{
name: "Automation",
color: "var(--color-cat-operations)",
blurb: "AI-driven generation, translation, forms",
},
{
name: "Advanced Formatting",
color: "var(--color-cat-healthcare)",
blurb: "TOC, overlays, format-to-PDF, PDF/A",
},
{
name: "Developer Tools",
color: "var(--color-text-5)",
blurb: "Repair, compress, metadata",
},
];
/* ──────────────────────────────────────────────────────────────────────── */
/* LIBRARY_OPS — full atomic operation catalogue */
/* ──────────────────────────────────────────────────────────────────────── */
export const LIBRARY_OPS: readonly LibraryOp[] = [
// Validation / Document Review
{
id: "validate",
label: "Schema validate",
icon: "shield",
kind: "validate",
category: "Validation",
desc: "Check required fields, business rules, and coverage gaps against the typed schema",
},
{
id: "authenticity",
label: "Authenticity",
icon: "check",
kind: "validate",
category: "Validation",
desc: "Verify the document is genuine (signature, issuer, watermark checks)",
},
{
id: "tamper-check",
label: "Tamper check",
icon: "alertTriangle",
kind: "validate",
category: "Validation",
desc: "Detect modifications since signing or last-known-good state",
},
{
id: "source-auth",
label: "Source auth",
icon: "lock",
kind: "validate",
category: "Validation",
desc: "Confirm the document came from an authenticated source",
},
{
id: "counterparty-match",
label: "Counterparty match",
icon: "userPlus",
kind: "validate",
category: "Validation",
desc: "Match the document counterparty against expected identity",
},
{
id: "confidence-check",
label: "Confidence bounds",
icon: "activity",
kind: "validate",
category: "Validation",
desc: "Gate downstream ops on extraction-confidence thresholds",
},
{
id: "inspect-basic",
label: "Basic info",
icon: "fileText",
kind: "validate",
category: "Document Review",
desc: "Get document metadata (title, author, page count, dates)",
},
{
id: "inspect-pages",
label: "Page count",
icon: "fileText",
kind: "validate",
category: "Document Review",
desc: "Return the page count of the document",
},
{
id: "inspect-fonts",
label: "Font info",
icon: "fileText",
kind: "validate",
category: "Document Review",
desc: "List fonts embedded in the document",
},
{
id: "inspect-security",
label: "Security info",
icon: "lock",
kind: "validate",
category: "Document Review",
desc: "Read security configuration (encryption, signatures, permissions)",
},
{
id: "inspect-form",
label: "Form fields",
icon: "fileText",
kind: "validate",
category: "Document Review",
desc: "Enumerate form fields and their values",
},
{
id: "filter-text",
label: "Filter by text",
icon: "shield",
kind: "validate",
category: "Validation",
desc: "Pass only docs containing specific text",
},
{
id: "filter-image",
label: "Filter by image",
icon: "shield",
kind: "validate",
category: "Validation",
desc: "Pass only docs containing images",
},
{
id: "filter-page-count",
label: "Filter by page count",
icon: "shield",
kind: "validate",
category: "Validation",
desc: "Pass only docs within a page-count range",
},
{
id: "filter-page-size",
label: "Filter by page size",
icon: "shield",
kind: "validate",
category: "Validation",
desc: "Pass only docs matching a page-dimension constraint",
},
{
id: "filter-file-size",
label: "Filter by file size",
icon: "shield",
kind: "validate",
category: "Validation",
desc: "Pass only docs within a file-size range",
},
// Modify / Page Formatting / Removal / Extraction / Advanced Formatting / Developer Tools
{
id: "merge",
label: "Merge",
icon: "layers",
kind: "modify",
category: "Page Formatting",
desc: "Combine multiple PDFs into a single document",
},
{
id: "split",
label: "Split",
icon: "grid",
kind: "modify",
category: "Removal",
desc: "Split a PDF into pages, sections, or by document boundary",
},
{
id: "split-chapters",
label: "Split by chapters",
icon: "grid",
kind: "modify",
category: "Removal",
desc: "Split a PDF along bookmark or chapter boundaries",
},
{
id: "split-size",
label: "Split by size",
icon: "grid",
kind: "modify",
category: "Removal",
desc: "Split a PDF when it exceeds a size or page-count threshold",
},
{
id: "auto-split",
label: "Auto split",
icon: "grid",
kind: "modify",
category: "Classification",
desc: "Detect document boundaries and split automatically",
},
{
id: "remove-pages",
label: "Remove pages",
icon: "grid",
kind: "modify",
category: "Removal",
desc: "Drop specified pages from the document",
},
{
id: "remove-blanks",
label: "Remove blank pages",
icon: "grid",
kind: "modify",
category: "Removal",
desc: "Detect and drop blank pages",
},
{
id: "rearrange-pages",
label: "Rearrange pages",
icon: "grid",
kind: "modify",
category: "Page Formatting",
desc: "Reorder pages by a specified sequence",
},
{
id: "rotate",
label: "Rotate",
icon: "arrowRight",
kind: "modify",
category: "Page Formatting",
desc: "Rotate pages or correct page orientation",
},
{
id: "crop",
label: "Crop",
icon: "layout",
kind: "modify",
category: "Page Formatting",
desc: "Crop pages to a region or trim margins",
},
{
id: "compress",
label: "Compress",
icon: "package",
kind: "modify",
category: "Developer Tools",
desc: "Reduce file size while preserving fidelity",
},
{
id: "flatten",
label: "Flatten",
icon: "layout",
kind: "modify",
category: "Page Formatting",
desc: "Flatten forms and annotations into the page content",
},
{
id: "repair",
label: "Repair",
icon: "package",
kind: "modify",
category: "Developer Tools",
desc: "Repair a corrupted or malformed PDF",
},
{
id: "remove-images",
label: "Remove images",
icon: "eye",
kind: "modify",
category: "Removal",
desc: "Strip images to reduce file size",
},
{
id: "update-metadata",
label: "Update metadata",
icon: "fileText",
kind: "modify",
category: "Developer Tools",
desc: "Edit PDF metadata fields (title, author, keywords)",
},
{
id: "auto-rename",
label: "Auto rename",
icon: "fileText",
kind: "modify",
category: "Classification",
desc: "Rename based on extracted document content",
},
{
id: "add-image",
label: "Add image",
icon: "penTool",
kind: "modify",
category: "Page Formatting",
desc: "Insert an image onto pages",
},
{
id: "add-stamp",
label: "Add stamp",
icon: "penTool",
kind: "modify",
category: "Page Formatting",
desc: "Apply a stamp to pages",
},
{
id: "add-page-numbers",
label: "Page numbers",
icon: "penTool",
kind: "modify",
category: "Page Formatting",
desc: "Add page numbers to the document",
},
{
id: "add-attachments",
label: "Add attachments",
icon: "penTool",
kind: "modify",
category: "Advanced Formatting",
desc: "Embed file attachments in the PDF",
},
{
id: "overlay",
label: "Overlay PDFs",
icon: "layers",
kind: "modify",
category: "Advanced Formatting",
desc: "Overlay one PDF on top of another",
},
{
id: "multi-page-layout",
label: "Multi-page layout",
icon: "grid",
kind: "modify",
category: "Advanced Formatting",
desc: "Combine multiple pages into a single page (n-up)",
},
{
id: "scale-pages",
label: "Scale pages",
icon: "layout",
kind: "modify",
category: "Page Formatting",
desc: "Resize pages to a target dimension",
},
{
id: "edit-toc",
label: "Edit TOC",
icon: "fileText",
kind: "modify",
category: "Advanced Formatting",
desc: "Edit the table of contents / bookmarks",
},
{
id: "scanner-effect",
label: "Scanner effect",
icon: "penTool",
kind: "modify",
category: "Page Formatting",
desc: "Apply a scanned-document visual effect",
},
{
id: "invert-colors",
label: "Invert colors",
icon: "penTool",
kind: "modify",
category: "Page Formatting",
desc: "Invert page colors or replace specific colors",
},
{
id: "convert-image",
label: "PDF → image",
icon: "fileText",
kind: "modify",
category: "Extraction",
desc: "Convert PDF pages to image files",
},
{
id: "image-to-pdf",
label: "Image → PDF",
icon: "fileText",
kind: "modify",
category: "Advanced Formatting",
desc: "Convert images into a PDF",
},
{
id: "convert-word",
label: "PDF → Word",
icon: "fileText",
kind: "modify",
category: "Extraction",
desc: "Convert PDF to a DOCX document",
},
{
id: "convert-pptx",
label: "PDF → presentation",
icon: "fileText",
kind: "modify",
category: "Extraction",
desc: "Convert PDF to a PPTX deck",
},
{
id: "convert-text",
label: "PDF → text",
icon: "fileText",
kind: "modify",
category: "Extraction",
desc: "Extract plain text from the PDF",
},
{
id: "convert-html",
label: "PDF → HTML",
icon: "fileText",
kind: "modify",
category: "Extraction",
desc: "Convert PDF to HTML",
},
{
id: "convert-xml",
label: "PDF → XML",
icon: "fileText",
kind: "modify",
category: "Extraction",
desc: "Convert PDF to structured XML",
},
{
id: "convert-csv",
label: "PDF → CSV",
icon: "fileText",
kind: "modify",
category: "Extraction",
desc: "Extract tables from the PDF as CSV",
},
{
id: "convert-markdown",
label: "PDF → Markdown",
icon: "fileText",
kind: "modify",
category: "Extraction",
desc: "Convert PDF to Markdown",
},
{
id: "convert-pdfa",
label: "PDF → PDF/A",
icon: "fileText",
kind: "modify",
category: "Advanced Formatting",
desc: "Convert to the PDF/A archival format",
},
{
id: "html-to-pdf",
label: "HTML → PDF",
icon: "fileText",
kind: "modify",
category: "Advanced Formatting",
desc: "Render HTML as a PDF",
},
{
id: "markdown-to-pdf",
label: "Markdown → PDF",
icon: "fileText",
kind: "modify",
category: "Advanced Formatting",
desc: "Render Markdown as a PDF",
},
{
id: "url-to-pdf",
label: "URL → PDF",
icon: "fileText",
kind: "modify",
category: "Advanced Formatting",
desc: "Render a web page as a PDF",
},
{
id: "eml-to-pdf",
label: "Email → PDF",
icon: "fileText",
kind: "modify",
category: "Advanced Formatting",
desc: "Convert an email file (.eml) to a PDF",
},
{
id: "ocr",
label: "OCR",
icon: "eye",
kind: "modify",
category: "Extraction",
desc: "Optical character recognition on scanned pages",
},
{
id: "extract-images",
label: "Extract images",
icon: "sparkles",
kind: "modify",
category: "Extraction",
desc: "Extract embedded images from the PDF",
},
{
id: "extract-bookmarks",
label: "Extract bookmarks",
icon: "sparkles",
kind: "modify",
category: "Extraction",
desc: "Extract the bookmark / outline tree",
},
{
id: "extract-scans",
label: "Extract scans",
icon: "sparkles",
kind: "modify",
category: "Extraction",
desc: "Detect and extract scanned image regions",
},
// Secure
{
id: "redact",
label: "Redact PII",
icon: "eye",
kind: "secure",
category: "Document Security",
desc: "Remove or mask PII before the document is stored or released",
},
{
id: "pii-enforce",
label: "PII / PHI enforcement",
icon: "shield",
kind: "secure",
category: "Document Security",
desc: "Policy check that outputs do not leak protected data beyond declared scope",
},
{
id: "auto-redact",
label: "Auto-redact",
icon: "eye",
kind: "secure",
category: "Document Security",
desc: "Automatically redact sensitive content based on policy",
},
{
id: "watermark",
label: "Confidentiality mark",
icon: "penTool",
kind: "secure",
category: "Document Security",
desc: "Apply a visible confidentiality watermark to pages",
},
{
id: "attribution-watermark",
label: "Attribution watermark",
icon: "penTool",
kind: "secure",
category: "Document Security",
desc: "Embed a per-recipient invisible watermark for leak tracing",
},
{
id: "sanitize",
label: "Sanitize",
icon: "shield",
kind: "secure",
category: "Document Security",
desc: "Strip hidden data, JavaScript, and metadata from the PDF",
},
{
id: "sign-output",
label: "Sign outputs",
icon: "penTool",
kind: "secure",
category: "Signing",
desc: "Apply a tamper-evident signature covering the artifact and run metadata",
},
{
id: "cert-sign",
label: "Certificate sign",
icon: "penTool",
kind: "secure",
category: "Signing",
desc: "Sign with a digital certificate",
},
{
id: "add-password",
label: "Add password",
icon: "lock",
kind: "secure",
category: "Document Security",
desc: "Password-protect the output PDF",
},
{
id: "encrypt-rest",
label: "Encryption at rest",
icon: "lock",
kind: "secure",
category: "Document Security",
desc: "AES-256 encryption on stored artifacts (Stirling-managed, BYOK, or HYOK)",
},
{
id: "flatten-secure",
label: "Flatten + lock",
icon: "layout",
kind: "secure",
category: "Document Security",
desc: "Flatten forms and lock the document against tampering",
},
{
id: "retention",
label: "Retention policy",
icon: "fileText",
kind: "secure",
category: "Document Security",
desc: "How long Stirling retains the artifact, run record, and audit trail",
},
{
id: "residency",
label: "Regional residency",
icon: "globe",
kind: "secure",
category: "Document Security",
desc: "Where the artifact is stored and processed (region-pin or air-gap)",
},
{
id: "access-policy",
label: "Access policy",
icon: "key",
kind: "secure",
category: "Document Security",
desc: "Who can fetch the sealed artifact (signed URL, IdP-gated, public)",
},
{
id: "remove-cert-sign",
label: "Remove signature",
icon: "penTool",
kind: "secure",
category: "Signing",
desc: "Strip a digital signature from the document",
},
// AI-powered ops
{
id: "summarize",
label: "Document summarizer",
icon: "fileText",
kind: "modify",
category: "Document Review",
provider: "claude",
desc: "Generate executive summaries and key insights",
},
{
id: "translate",
label: "Document translator",
icon: "globe",
kind: "modify",
category: "Automation",
provider: "claude",
desc: "Translate documents while preserving formatting",
},
{
id: "contract-analyze",
label: "Contract analyzer",
icon: "shield",
kind: "validate",
category: "Document Review",
provider: "claude",
desc: "Review contracts for risks, obligations, and key terms",
},
{
id: "compliance-audit",
label: "Compliance auditor",
icon: "check",
kind: "validate",
category: "Document Review",
provider: "stirling",
desc: "Check documents against regulatory requirements",
},
{
id: "generate",
label: "Document generation",
icon: "penTool",
kind: "modify",
category: "Automation",
provider: "claude",
desc: "Create contracts, reports, and business documents from prompts",
},
{
id: "certify",
label: "Document certification",
icon: "check",
kind: "validate",
category: "Document Review",
provider: "claude",
desc: "Review documents against criteria and certify approval",
},
{
id: "intelligent-forms",
label: "Intelligent forms",
icon: "fileText",
kind: "modify",
category: "Automation",
provider: "stirling",
desc: "Auto-fill forms and extract submissions",
},
// Schema-aware ops (D35)
{
id: "schema-extract",
label: "Schema-aware extract",
icon: "sparkles",
kind: "modify",
category: "Classification",
desc: "Extract the typed fields the inferred schema declares — not heuristic regexes. Confidence scored per field",
},
{
id: "schema-validate",
label: "Schema validate",
icon: "shield",
kind: "validate",
category: "Validation",
desc: "Validate the document against its inferred schema. Required-field coverage, type-correctness, business rules",
},
{
id: "schema-route",
label: "Conditional routing",
icon: "arrowRight",
kind: "validate",
category: "Classification",
desc: "Fork to a downstream pipeline based on the inferred schema (Invoice → AP, Loan Closing → Compliance, etc.)",
},
{
id: "drift-check",
label: "Schema drift check",
icon: "alertTriangle",
kind: "validate",
category: "Validation",
desc: "Flag when a doc claims to be a known type but its field shape diverges from prior examples",
},
{
id: "smart-redact",
label: "Field-aware redact",
icon: "eye",
kind: "secure",
category: "Document Security",
desc: "Schema-aware PII redaction — targets fields the schema declares as PII, not regex fishing",
},
{
id: "field-confidence",
label: "Field confidence",
icon: "activity",
kind: "validate",
category: "Validation",
desc: "Per-field confidence scoring backed by the typed schema",
},
{
id: "schema-split",
label: "Schema-aware split",
icon: "grid",
kind: "modify",
category: "Classification",
desc: "Split a multi-doc bundle along schema boundaries",
},
{
id: "smart-watermark",
label: "Smart watermark",
icon: "penTool",
kind: "secure",
category: "Document Security",
desc: "Watermark text incorporates extracted schema fields",
},
];
export const LIBRARY_OPS_INDEX: Record<string, LibraryOp> = Object.fromEntries(
LIBRARY_OPS.map((op) => [op.id, op]),
);
export function lookupLibraryOp(id: string): LibraryOp | PipelineOp | null {
return LIBRARY_OPS_INDEX[id] ?? lookupOp(id);
}
/* ──────────────────────────────────────────────────────────────────────── */
/* SOURCES + DESTINATIONS — the rails */
/* ──────────────────────────────────────────────────────────────────────── */
export const SOURCE_OPTIONS: readonly SourceOption[] = [
{
id: "upload",
label: "Upload API",
icon: "upload",
desc: "POST documents to a Stirling endpoint",
},
{
id: "webhook",
label: "Inbound webhook",
icon: "plug",
desc: "Receive documents from another system via webhook",
},
{
id: "s3",
label: "S3 bucket watch",
icon: "server",
desc: "Poll an S3 bucket for new files",
},
{
id: "email",
label: "Email intake",
icon: "fileText",
desc: "Route an inbox to this pipeline",
},
{
id: "scheduled",
label: "Scheduled import",
icon: "activity",
desc: "Fetch from SFTP/URL on a schedule",
},
];
export const DESTINATION_OPTIONS: readonly DestinationOption[] = [
{
id: "vault",
label: "Stirling vault",
icon: "shield",
desc: "Store the processed document and extracted data in Stirling",
},
{
id: "s3",
label: "S3 bucket",
icon: "server",
desc: "Write results to an S3 bucket",
},
{
id: "webhook",
label: "Outbound webhook",
icon: "externalLink",
desc: "POST results to a URL you control",
},
{
id: "pipeline",
label: "Another pipeline",
icon: "arrowRight",
desc: "Chain to a second pipeline",
},
{
id: "database",
label: "Database",
icon: "database",
desc: "Insert extracted fields into Postgres/Snowflake/BigQuery",
},
{
id: "sftp",
label: "SFTP",
icon: "fileText",
desc: "Drop output files on an SFTP server",
},
];
export const lookupSource = (id: string) =>
SOURCE_OPTIONS.find((s) => s.id === id) ?? null;
export const lookupDestination = (id: string) =>
DESTINATION_OPTIONS.find((d) => d.id === id) ?? null;