diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b7944ce..99a32f5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -109,12 +109,17 @@ jobs: # Tag + GitHub Release whenever ANY publish happened. Tag scheme # branches on what actually shipped so a one-package bump can't # collide with a stale tag from a prior multi-package run: - # cli shipped → v$CLI_VERSION (CLI is the user-facing handle - # for `npx openhop@beta`, so its - # version anchors the combined - # release name) - # server only → server/v$SERVER_VERSION - # web only → web/v$WEB_VERSION + # cli shipped → v$CLI_VERSION (CLI is the user-facing + # handle for `npx + # openhop@beta`, so its + # version anchors the + # combined release) + # server (± web) → server/v$SERVER_VERSION (server is the lowest + # layer and a more + # meaningful anchor + # than web when both + # ship together) + # web only → web/v$WEB_VERSION - name: Create tag + GitHub Release # Gate on actual step outcomes, not the pre-check intent — a failed # `npm publish` (network, auth, registry rejection) shouldn't still @@ -138,10 +143,11 @@ jobs: run: | if [ "$CLI_PUBLISHED" = "true" ]; then tag="v$CLI_VERSION" - elif [ "$WEB_PUBLISHED" = "true" ]; then - tag="web/v$WEB_VERSION" - else + elif [ "$SERVER_PUBLISHED" = "true" ]; then + # Covers server-only AND server+web combined runs. tag="server/v$SERVER_VERSION" + else + tag="web/v$WEB_VERSION" fi # If THIS tag already exists (re-run of a workflow whose tag diff --git a/packages/web/src/AppFragment.tsx b/packages/web/src/AppFragment.tsx index b3e862f..24d0cd8 100644 --- a/packages/web/src/AppFragment.tsx +++ b/packages/web/src/AppFragment.tsx @@ -5,7 +5,8 @@ import { FlowCanvas } from './components/FlowCanvas' import { DataInspectionPanel, BookmarkTab, type DockSide } from './components/DataInspectionPanel' import { FlowEditorModal } from './components/FlowEditorModal' import { buildStarterYaml } from './lib/starter-yaml' -import { buildShareUrl, decodeFragment } from './lib/share-url' +import { buildShareUrl, decodeFragment, encodeFragment } from './lib/share-url' +import { EXAMPLE_FLOWS } from './lib/example-flows' import type { FlowNode, FlowStep, FlowData, Flow } from './types' interface FlowNavItem { @@ -328,13 +329,36 @@ export default function AppFragment() { )} {!decodedFlow ? (
-
+

{decodeError ? 'No flow loaded' : 'No flow shared'}

-

- Open a flow by visiting a share URL, or click "+ New flow" above to create one. +

+ Open a flow by visiting a share URL, click "+ New flow" above, or pick a + pre-built example:

+
    + {EXAMPLE_FLOWS.map((ex) => ( +
  • + +
  • + ))} +
) : displayFlow ? ( diff --git a/packages/web/src/components/DataPixel.tsx b/packages/web/src/components/DataPixel.tsx index 8d75c4b..9d3715d 100644 --- a/packages/web/src/components/DataPixel.tsx +++ b/packages/web/src/components/DataPixel.tsx @@ -3,7 +3,9 @@ import { useViewport } from '@xyflow/react' import type { FlowStep, FlowData } from '../types' import { DataTooltip } from './DataTooltip' -const CARROT_SPRITE = '/sprites/carrot_pixels.svg' +// Use Vite's BASE_URL so the sprite resolves under the project base on +// the Pages deploy (`/openhop/sprites/...`) and at the root in dev (`/`). +const CARROT_SPRITE = `${import.meta.env.BASE_URL}sprites/carrot_pixels.svg` const DEFAULT_PIXEL_COLOR = '#ff8a4a' // VARIANT_ACCENT[0] — sprite's original orange. interface DataPixelProps { diff --git a/packages/web/src/components/nodes/node-sprites.ts b/packages/web/src/components/nodes/node-sprites.ts index 9b5693c..9835dd1 100644 --- a/packages/web/src/components/nodes/node-sprites.ts +++ b/packages/web/src/components/nodes/node-sprites.ts @@ -7,20 +7,27 @@ export interface BuildingProps { active?: boolean } +// Vite's BASE_URL is `/` in dev and `/openhop/` on the GitHub Pages +// deploy. Without it, root-absolute sprite paths 404 on Pages because +// the page lives under /openhop/ but `/sprites/foo.svg` resolves to the +// site root, not the project base. BASE_URL always ends with `/`, so +// `${BASE}sprites/foo.svg` is the correct concatenation. +const BASE = import.meta.env.BASE_URL + // Sprite per node type. Types not in this map fall back to the service sprite // at render time (see FlowNode.tsx). export const NODE_TYPE_SPRITE: Record = { - actor: '/sprites/user_node.svg', - endpoint: '/sprites/endpoint_node.svg', - auth: '/sprites/auth_node.svg', - database: '/sprites/database_node.svg', - external: '/sprites/external_node.svg', - cache: '/sprites/cache_node.svg', - queue: '/sprites/queue_node.svg', - service: '/sprites/service_node.svg', - docker: '/sprites/docker_node.svg', - k8s: '/sprites/k8s_node.svg', - scheduler: '/sprites/scheduler_node.svg', + actor: `${BASE}sprites/user_node.svg`, + endpoint: `${BASE}sprites/endpoint_node.svg`, + auth: `${BASE}sprites/auth_node.svg`, + database: `${BASE}sprites/database_node.svg`, + external: `${BASE}sprites/external_node.svg`, + cache: `${BASE}sprites/cache_node.svg`, + queue: `${BASE}sprites/queue_node.svg`, + service: `${BASE}sprites/service_node.svg`, + docker: `${BASE}sprites/docker_node.svg`, + k8s: `${BASE}sprites/k8s_node.svg`, + scheduler: `${BASE}sprites/scheduler_node.svg`, } export const SPRITE_SIZE = 108 diff --git a/packages/web/src/globals.d.ts b/packages/web/src/globals.d.ts index 9cd1e02..3c1809f 100644 --- a/packages/web/src/globals.d.ts +++ b/packages/web/src/globals.d.ts @@ -1,5 +1,12 @@ /** Global ambient declarations for OpenHop's web bundle. */ +// Vite's `?raw` query — load a file as its raw text contents at build +// time. Used by example-flows.ts to embed YAML examples in the bundle. +declare module '*.yaml?raw' { + const content: string + export default content +} + declare global { interface Window { /** Test/debug hook: scales animation speed (default 1, e.g. 4 for 4× faster). */ diff --git a/packages/web/src/lib/example-flows.ts b/packages/web/src/lib/example-flows.ts new file mode 100644 index 0000000..0062e3b --- /dev/null +++ b/packages/web/src/lib/example-flows.ts @@ -0,0 +1,40 @@ +/** + * Curated example flows shown on the empty state of the Pages deploy. + * + * Each entry is the same YAML that lives in `examples/*.yaml` at the + * repo root, imported as a raw string via Vite's `?raw` query so the + * source-of-truth stays in one place. Vite inlines these into the + * bundle — together they're ~6 KB which is comfortable to ship. + */ + +import authFlow from '../../../../examples/auth-flow.yaml?raw' +import orderFlow from '../../../../examples/order-flow.yaml?raw' +import simpleCrud from '../../../../examples/simple-crud.yaml?raw' + +export interface ExampleFlow { + id: string + title: string + description: string + yaml: string +} + +export const EXAMPLE_FLOWS: ExampleFlow[] = [ + { + id: 'simple-crud', + title: 'Simple CRUD', + description: 'Basic REST API CRUD — the smallest useful flow.', + yaml: simpleCrud, + }, + { + id: 'auth-flow', + title: 'OAuth2 Login', + description: 'Browser → app → Google OAuth → DB + cache.', + yaml: authFlow, + }, + { + id: 'order-flow', + title: 'Order Processing', + description: 'Multi-service order pipeline with payment, audit, and retry.', + yaml: orderFlow, + }, +]