OpenSOP API Reference — Redesign Spec
Source design: /Users/abrahamkuri/Downloads/OpenSOP API Docs - standalone.html
(a Pencil/.pen bundled standalone HTML — open in a browser to view)
This is the source of truth for the rebuild. The design must match exactly.
1. Scope
Replace the current single-page /api-docs (one ERB view, simple table) with
a multi-page reference site that lives at /api-docs and uses its OWN custom
layout (no main app sidebar, no activity rail). The current
Ui::ApiDocsController#index and app/views/ui/api_docs/index.html.erb get
fully replaced.
Pages:
- Guides (long-form prose):
quickstart— landing, "From zero to a running process instance in
under five minutes." 4 numbered steps + Lifecycle diagram + Next stepsauthentication— three auth modes (bearer / HMAC / single-use callback),
Token rotation tableprocess-format— YAML schema anatomy, Step-types table, References ($${...} syntax)errors— error JSON Shape, Status-codes table, Retriesversioning— API version table, Process versions
- Endpoint detail pages (one per endpoint, dark code panel on the right):
- DISCOVERY:
GET /sop/,GET /sop/:name/schema - PROCESSES:
POST /processes/register - INSTANCES:
GET /sop/instances,POST /sop/:name/start,
GET /sop/:name/:id,POST /sop/:name/:id/cancel - STEPS:
GET /sop/:name/:id/steps,
POST /sop/:name/:id/steps/:step_id/submit,GET /sop/steps/pending - WEBHOOK TRIGGERS:
POST /sop/triggers/:process_name - WEBHOOK CALLBACKS:
POST /sop/webhooks/:callback_id - METRICS:
GET /sop/metrics
- DISCOVERY:
2. Layout (custom — layouts/api_docs.html.erb)
┌──────────────────────────────────────────────────────────────────┐
│ TOPBAR (h=48) ▣ OpenSOP [API Reference] (v0.1) ... [search]│
├────────┬─────────────────────────────────────────────────────────┤
│SIDEBAR │ MAIN content (page-specific) │
│ 240px │ - guides: 1 col │
│ #f7f8fa│ - endpoints: 2 cols (left prose ~58%, right code 42%) │
│ │ │
└────────┴─────────────────────────────────────────────────────────┘
- Topbar background:
#fff, bottom border#eaecef, height 48px, padding-x 24 - Sidebar background:
#f7f8fa, right border#eaecef, width 240px, full height - Main content max-width naturally fills remaining width; content-area
uses 32–40px horizontal padding
No main SidebarComponent, no ActivityRailComponent. Set
show_rail? is irrelevant — this layout does not render the standard chrome.
3. Topbar
Left to right:
- Brand: 4-square 20×20 grid logo (top-left + bottom-right
#0f1115,
the other two#5d6470). Each square 9×9 with 2px gap. - Wordmark: "OpenSOP" — Inter 14px/600
#0f1115 - Section tag: "API Reference" — Inter 12px/500
#5d6470 - Version pill: "v0.1" — mono 11px in
#eef0f3pill, padding 2/6, radius 3 - Spacer (flex-grow)
- Nav links: Dashboard, Changelog, GitHub (with octocat svg) — Inter 12px
#5d6470hover#0f1115. Dashboard goes toui_dashboard_path.
GitHub goes tohttps://github.com/Chosen9115/opensop(use Opensop config if exists). - Search input: width ~280px,
#fffbg, border#eaecef, radius 3,
height 28, padding-l 32 (search icon left), placeholder "Search reference…"
#9aa0a8, with⌘Kkbd hint at the right edge (mono 10px in pill).
The search box is a non-functional UI placeholder for now (matches the
design — clicking it focuses; no real search backend). Stub it as a plain
input with no submit handler. Capture the path for future enhancement.
4. Sidebar
Sections with header label + count, then link rows. Section spacing 16px.
GETTING STARTED 5
Quickstart →
Authentication →
Process format →
Errors →
Versioning →
DISCOVERY 2
GET /
GET /:name/schema
PROCESSES 1
POST /processes/register
INSTANCES 4
GET /instances
POST /:name/start ← active
GET /:name/:id
POST /:name/:id/cancel
STEPS 3
GET /:name/:id/steps
POST /:name/:id/steps/:s…
GET /steps/pending
WEBHOOK TRIGGERS 1
POST /triggers/:process_…
WEBHOOK CALLBACKS 1
POST /webhooks/:callback…
METRICS 1
GET /metrics
.sb-section — uppercase 9px #9aa0a8 letter-spaced 0.06em with the
count as a faint mono pill on the right.
.sb-link — 28px row height, padding-l 14, padding-r 8, font 12.
Default: text #5d6470. Hover: bg #f0f2f5. Active (.on):
- bg
#fff - left rail: 2px wide black bar at left edge (use
box-shadow: inset 2px 0 0 #0f1115) - text color
#0f1115font-weight 500 - subtle shadow
0 1px 2px rgba(0,0,0,0.04)
Guide links (.sb-link.guide): label + → arrow on right (#9aa0a8).
Endpoint links: small <span class="meth get">GET</span> (mono 9px,
weight 600, color #1f4ed8 for GET, #0f7a3d for POST, #b03333 for DELETE)
- truncated path in mono 11px
#0f1115. Long paths show ellipsis.
5. Page header (every page)
api / discovery / list all processes ← breadcrumb (mono 11px #9aa0a8, '/' #d5d8de)
List all processes ← <h1> Inter 28px/600 #0f1115
[GET] /sop/ ← method badge + mono path (only on endpoint pages)
A short prose description... ← Inter 14px/400 #5d6470 max-w prose
─────────────────────────────────────────── ← divider #eaecef
Breadcrumb spacing: 2px between segments and slashes. Slashes are
plain / in faint color.
6. Components & shared elements
MethodBadge (<span class="meth-badge">)
GET bg #e3ebfd fg #1f4ed8
POST bg #e7f6ec fg #0f7a3d
DELETE bg #fbe9e9 fg #b03333
PATCH bg #fdf3d6 fg #946700
Shape: rounded 3px, padding 3px 8px, mono 10px weight 600.
On endpoint pages the badge sits to the LEFT of the inline path, with
8px gap. Path itself is mono 14px #0f1115. Use :name highlighted
distinctly (slightly different color e.g. #1f4ed8 for params).
Callout (<div class="callout callout--info">)
Left-bordered colored block. Variants:
- info — bg
#e3ebfd80(or#eef4ff), border-left4px #1f4ed8, label "AUTH" or "PREREQ" - warn — bg
#fdf3d680, border-left4px #946700, label "IMPORTANT"
Padding 12 16, radius 3 (right side only). Label is mono 10px weight 700
uppercase, inline before body text. 8px gap between label and body.
CodePanel (<div class="code-panel">)
Dark theme block. Background #0f1115, border #2a2f3a (1px), radius 4.
Header bar (h=36, border-b #2a2f3a, padding-x 12):
- Left: tabs OR a static label (e.g. "RESPONSE" or "REQUEST BODY · application/json")
- Right:
Copybutton — mono 10px#a4abb8with copy icon, hover#fff
Tabs: curl / node / python / ruby — mono 11px, padding 6/10, active tab
has #1a1d24 bg + #fff fg + 1px bottom highlight #1f4ed8.
Body (padding 16, mono 12px, line-height 1.6):
Syntax tokens (use spans):
- key:
#b6c1d6 - string:
#c5d99a - number:
#f1c075 - comment:
#6b7281 - type/built-in:
#a8c5ff - punct:
#a4abb8
Response bodies have a small "RESPONSE 200" ribbon on top using .code-resp-head
with #0f7a3d "200" pill.
Status-code badges (<span class="status status--ok|warn|err">)
- 200/201 — bg
#e7f6ecfg#0f7a3d(mono 11px weight 600 padding 2/8) - 400/422/429 — bg
#fdf3d6fg#946700 - 401/403/404/409 — bg
#fdf3d6fg#946700 - 500 — bg
#fbe9e9fg#b03333
Param list (<dl class="param-list">)
Vertical list. For each param:
name string REQUIRED ← <dt>: name mono 13 #0f1115, type mono 11 #5d6470, required tag mono 9 weight 700 #b03333 in #fbe9e9 pill
Description text here. ← <dd>: 13px #5d6470
Stack with 16px gap. No table grid.
Param row table — used on guides (e.g. error status codes, lifecycle)
Plain HTML table with <th> mono 10px uppercase tracking #9aa0a8, <td>
13px #0f1115 (mono for codes), row separator border-b #eaecef, padding
12/16, no outer borders.
Numbered step (<div class="step">)
Used in Quickstart. Square 28x28 dark #0f1115 with white digit (Inter 13px
weight 600), then heading right (Inter 14px weight 600 #0f1115), then
prose description and code below — indented under the heading, not under
the number column.
Lifecycle diagram (Quickstart)
SVG of state machine: pending → running → waiting → running → completed
with on error → failed shown as a dashed red branch. Each state is a
mono pill in a thin gray-bordered rounded rect; completed has a green
border. Dashes & arrows in #9aa0a8. Use a single inline SVG in the
quickstart partial.
7. Typography
Body Inter 13px / 1.5 #0f1115
H1 (page) Inter 28px / 600 #0f1115
H2 (section) Inter 18px / 600 #0f1115 (margin-top 32, margin-bottom 12)
H3 Inter 14px / 600 #0f1115
Mono JetBrains Mono 12px (default code), 11–13px in context
Inter and JetBrains Mono are already loaded by layouts/application.html.erb.
The custom layout must include the same Google Fonts <link>.
8. Color tokens (already in app/assets/tailwind/application.css)
| token | hex |
|---|---|
| bg | #fff |
| bg-sunken | #f7f8fa |
| bg-pill | #eef0f3 |
| border | #eaecef |
| border-strong | #d5d8de |
| fg | #0f1115 |
| fg-muted | #5d6470 |
| fg-faint | #9aa0a8 |
| ok / ok-bg | #0f7a3d / #e7f6ec |
| info / info-bg | #1f4ed8 / #e3ebfd |
| warn / warn-bg | #946700 / #fdf3d6 |
| err / err-bg | #b03333 / #fbe9e9 |
If the design needs a dark code surface, add --color-bg-code-dark: #0f1115
and --color-border-code: #2a2f3a to the Tailwind theme — these aren't
present yet (existing bg-code is #f7f8fa for the light snippets used
elsewhere).
Syntax-highlight color tokens for inline classes (.tk-key, .tk-str, …)
should be added once in a small CSS file colocated with the api-docs view.
9. Interactivity
Stimulus controller: api-docs--code-tabs
- Targets:
tab(each button),pane(each pre/code block) - Action:
click->api-docs--code-tabs#switch - On click: set
data-activeon the matching tab, hide other panes via
.hidden. Default-active = first tab (curl). - Single-controller-per-CodePanel — one panel may have its own tabs
while another (e.g. RESPONSE-only) renders without tabs.
Stimulus controller: api-docs--copy
- Target:
source(the<pre>) - Action:
click->api-docs--copy#copy - Reads
this.sourceTarget.innerText, callsnavigator.clipboard.writeText,
toggles button label to "Copied" for 1.2s.
Sidebar nav
- Active link: server-renders the
.onmodifier based oncurrent_path.
No JS toggling. - Endpoint section toggles? — No. All sections always expanded; this
matches the design.
Search input
- Pure visual stub. NO submit handler, NO controller. Document the
intent in a comment so a future agent can hook it up.
10. Routes
Replace
get "/api-docs", to: "api_docs#index", as: :api_docs
with
scope "/api-docs", as: :api_docs do
root to: "api_docs#index" # api_docs
get "guides/:slug", to: "api_docs#guide", as: :guide,
constraints: { slug: /[a-z][a-z0-9_-]*/ }
get "endpoints/:slug", to: "api_docs#endpoint", as: :endpoint,
constraints: { slug: /[a-z][a-z0-9_-]*/ }
end
#index redirects (or renders) Quickstart.
11. Catalog (single source of truth)
Add app/services/ui/api_docs/catalog.rb exposing:
module Ui::ApiDocs
module Catalog
GUIDES = [
{ slug: "quickstart", label: "Quickstart" },
{ slug: "authentication", label: "Authentication" },
{ slug: "process-format", label: "Process format" },
{ slug: "errors", label: "Errors" },
{ slug: "versioning", label: "Versioning" }
].freeze
ENDPOINT_SECTIONS = [
{ key: :discovery, label: "Discovery", endpoints: [
{ slug: "list-processes", method: "GET", path: "/sop/", summary_key: "list_processes" },
{ slug: "process-schema", method: "GET", path: "/sop/:name/schema", summary_key: "process_schema" }
]},
{ key: :processes, label: "Processes", endpoints: [
{ slug: "register-process", method: "POST", path: "/sop/processes/register", summary_key: "register_process" }
]},
{ key: :instances, label: "Instances", endpoints: [
{ slug: "list-instances", method: "GET", path: "/sop/instances", summary_key: "list_instances" },
{ slug: "start-instance", method: "POST", path: "/sop/:name/start", summary_key: "start_instance" },
{ slug: "show-instance", method: "GET", path: "/sop/:name/:id", summary_key: "show_instance" },
{ slug: "cancel-instance",method: "POST", path: "/sop/:name/:id/cancel", summary_key: "cancel_instance"}
]},
{ key: :steps, label: "Steps", endpoints: [
{ slug: "list-steps", method: "GET", path: "/sop/:name/:id/steps", summary_key: "list_steps" },
{ slug: "submit-step", method: "POST", path: "/sop/:name/:id/steps/:step_id/submit", summary_key: "submit_step" },
{ slug: "pending-steps", method: "GET", path: "/sop/steps/pending", summary_key: "pending_steps" }
]},
{ key: :webhook_triggers, label: "Webhook triggers", endpoints: [
{ slug: "fire-trigger", method: "POST", path: "/sop/triggers/:process_name", summary_key: "fire_trigger" }
]},
{ key: :webhook_callbacks, label: "Webhook callbacks", endpoints: [
{ slug: "deliver-callback", method: "POST", path: "/sop/webhooks/:callback_id", summary_key: "deliver_callback" }
]},
{ key: :metrics, label: "Metrics", endpoints: [
{ slug: "show-metrics", method: "GET", path: "/sop/metrics", summary_key: "show_metrics" }
]}
].freeze
def self.guide(slug); GUIDES.find { |g| g[:slug] == slug }; end
def self.endpoint(slug); ENDPOINT_SECTIONS.flat_map { |s| s[:endpoints] }.find { |e| e[:slug] == slug }; end
end
end
The controller passes the catalog to the sidebar partial each render.
12. Endpoint page content shape
Each endpoint partial follows:
crumbs (api / <section> / <endpoint label>)
H1 (endpoint label, e.g. "Start an instance")
[METHOD] /path (with :param highlighted)
prose (1–2 paragraphs)
callout (AUTH — Requires X-SOP-Token. See Authentication.)
H2 PATH PARAMETERS (mono uppercase 11px)
param list
H2 REQUEST BODY (only for POST)
prose / param list
H2 Response
status row (e.g. 200 application/json — Returns…)
H2 Errors
error rows (status code badge + code identifier + meaning)
Right column (sticky):
- Code panel "Request" — tabs: curl / node / python / ruby
- Code panel "Request body" — only for POST/PATCH bodies
- Code panel "Response" with status pill in header
Right column should position: sticky; top: 64, max-height = viewport - top
- 24, scroll-y inside.
For wide screens (≥ 1280) two-column. Below that, stack right column under
the prose with lg:grid-cols-[1fr_minmax(0,420px)] etc.
13. Files to create / modify
New files:
app/views/layouts/api_docs.html.erb
app/views/ui/api_docs/_topbar.html.erb
app/views/ui/api_docs/_sidebar.html.erb
app/views/ui/api_docs/_page_header.html.erb
app/views/ui/api_docs/_callout.html.erb
app/views/ui/api_docs/_code_panel.html.erb
app/views/ui/api_docs/index.html.erb (REPLACE)
app/views/ui/api_docs/guides/_quickstart.html.erb
app/views/ui/api_docs/guides/_authentication.html.erb
app/views/ui/api_docs/guides/_process_format.html.erb
app/views/ui/api_docs/guides/_errors.html.erb
app/views/ui/api_docs/guides/_versioning.html.erb
app/views/ui/api_docs/endpoints/_<each_slug>.html.erb (12 partials)
app/views/ui/api_docs/guide.html.erb (renders by slug)
app/views/ui/api_docs/endpoint.html.erb (renders by slug)
app/services/ui/api_docs/catalog.rb
app/javascript/controllers/api_docs_code_tabs_controller.js
app/javascript/controllers/api_docs_copy_controller.js
spec/requests/ui/api_docs_spec.rb (UPDATE — coverage for all routes)
Modify:
app/controllers/ui/api_docs_controller.rb (add #guide, #endpoint)
config/routes/ui.rb (replace single get)
config/locales/opensop.en.yml (full content tree)
app/javascript/controllers/index.js (register new controllers)
app/components/sidebar_component.rb (no change unless link target changes — but `ui_api_docs_path` should still resolve)
app/assets/tailwind/application.css (add minor api-docs scope, if needed)
14. Acceptance — must pass before "done"
- Visiting
/api-docsrenders the new layout (custom topbar + 240px sidebar,
no main app sidebar/rail) and shows Quickstart content. - Visiting
/api-docs/guides/authenticationshows the Authentication page. - Visiting
/api-docs/endpoints/start-instanceshows the Start-an-instance
endpoint page with right-side dark code panel. - Sidebar active state: only the current page has the
.onstyle. - Code panel tabs (curl/node/python/ruby) switch panes on click.
- Copy button copies code to clipboard and briefly shows "Copied".
- Method-badge colors match the spec exactly.
- The page renders cleanly at 1440 (desktop), and stacks the code panel
under prose at 1024 and below. bundle exec rspec spec/requests/ui/api_docs_spec.rbis green and
covers: index, every guide route, every endpoint route, an unknown
slug returns 404.- The static-design HTML at the source path and the rendered Rails
page should be visually indistinguishable when compared screenshot-
by-screenshot for at least Quickstart, Authentication, and one
endpoint detail.
15. Conventions to obey
- Tailwind utilities only — NO inline
style="". - I18n: every visible string under
opensop.api_docs.*. Use full key
paths:t('opensop.api_docs.guides.quickstart.title'). Use*_html
suffix for keys that contain markup. - ViewComponent or partial — partials are fine for one-off layout pieces;
use a ViewComponent only if a piece is reused with different params
across guides AND endpoints (e.g. CodePanel deserves a component;
PageHeader is a partial). - Heroicons for icons — inline SVG, sized 16×16 default.
- Stimulus controller files use kebab-case in
data-controller(e.g.
data-controller="api-docs--code-tabs") and snake_case filenames
(api_docs_code_tabs_controller.js). - All copy lives in i18n. Do not commit hardcoded body text in ERB.