Skip to content

feat: friendly component IDs with display names#81

Merged
TerrifiedBug merged 4 commits intomainfrom
friendly-component-ids-416
Mar 10, 2026
Merged

feat: friendly component IDs with display names#81
TerrifiedBug merged 4 commits intomainfrom
friendly-component-ids-416

Conversation

@TerrifiedBug
Copy link
Owner

Summary

  • Decouples pipeline component identity from user-facing names. Component keys are now auto-generated ({type}_{nanoid(8)}) and immutable. A new displayName field lets users rename components without redeploying.
  • Renaming a component requires only a save, not a redeploy — metrics, YAML generation, and GitOps are unaffected since they use the stable component key.
  • Existing pipelines require no migration — displayName is nullable and the GUI falls back to showing componentKey when it's null.

Changes

Schema: Add displayName String? to PipelineNode

Backend: Thread displayName through save, deploy snapshots, rollback, discard, copy-pipeline-graph, templates, and metrics responses

Frontend: Replace "Component Key" input with "Name" + read-only "Component ID"; update all node components to show displayName ?? componentKey; replace updateNodeKey with updateDisplayName; switch key generation from {type}_{timestamp} to {type}_{nanoid(8)}

Docs: Update pipeline YAML reference, editor guide, first pipeline tutorial, API reference, and templates docs

Test plan

  • Add a new source — verify node shows human name (e.g. "HTTP Server"), detail panel shows "Name" field and read-only "Component ID"
  • Rename a component — verify save works without deploy prompt, name persists after reload
  • Open an existing pipeline with old-style keys — verify nodes show componentKey as fallback
  • Copy/paste a node — verify pasted node gets a new Component ID but keeps the display name
  • Deploy a pipeline — verify metrics overlay still works with new-style keys
  • Rename a component after deploy — verify metrics continue flowing (no redeploy needed)
  • Clone/promote a pipeline — verify display names are preserved
  • Rollback a pipeline version — verify display names are restored from snapshot

Spec for decoupling pipeline component identity from user-facing
display names. Component keys become immutable UUID-based identifiers;
a new displayName field provides the human-readable label in the GUI.
Add a displayName field to PipelineNode so users can rename components
in the GUI without triggering a redeploy. Component keys are now
auto-generated using nanoid ({type}_{nanoid(8)}) and are immutable
after creation. The display name defaults to the component type's
human name (e.g. "HTTP Server") and is purely cosmetic.

- Add displayName String? column to PipelineNode schema
- Add generateComponentKey utility using nanoid
- Replace updateNodeKey with updateDisplayName in flow store
- Update all copy/paste/duplicate operations
- Thread displayName through save, deploy, rollback, discard, and
  template operations
- Update node components to show displayName ?? componentKey
- Replace "Component Key" input with "Name" + read-only "Component ID"
- Update docs (pipeline YAML reference, editor guide, tutorial, API
  reference, templates)
@github-actions github-actions bot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file feature and removed documentation Improvements or additions to documentation labels Mar 10, 2026
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 10, 2026

Greptile Summary

This PR decouples pipeline component identity from user-facing labels by introducing an immutable componentKey (generated via nanoid) and a new nullable displayName field. The feature is well-scoped: YAML generation, metrics matching, agent protocol, and GitOps are all unchanged since they continue to use componentKey; only the GUI label and save payload are affected.

  • Migration & schema: The ALTER TABLE "PipelineNode" ADD COLUMN "displayName" TEXT migration is now committed and will be applied automatically on startup. The Prisma schema and Zod input schemas (nodeSchema, templateNodeSchema) are updated consistently.
  • Key generation: generateComponentKey uses a custom nanoid alphabet (0-9A-Za-z, no hyphens) ensuring all generated keys satisfy the existing ^[a-zA-Z_][a-zA-Z0-9_]*$ regex. The old {type}_{Date.now()} pattern is replaced in addNode, single-paste, and multi-paste paths.
  • Display name threading: displayName is correctly carried through save, discard/restore, rollback, copy-pipeline-graph (clone/promote), deploy snapshot, and template save. All node renders now use displayName || componentKey, correctly falling back for both null/undefined and empty-string values.
  • Store: updateDisplayName follows the same history-push pattern as other state-mutating actions, and computeFlowFingerprint captures displayName via its generic Object.entries filter — renames correctly trigger the "unsaved changes" state.
  • Metrics: getComponentMetrics correctly surfaces displayName in its response. getNodePipelineRates fetches displayName from the DB but the field is not included in the returned rates object — the extra select is wasted work.

Confidence Score: 4/5

  • Safe to merge — all critical path changes (migration, node renders, save/rollback/clone flows) are implemented correctly.
  • The implementation is thorough and self-consistent. Previously flagged issues (missing migration file, ?? vs || empty-string fallback) have been resolved. The one remaining minor imperfection is that getNodePipelineRates fetches displayName from the DB but the field is silently discarded rather than returned or removed from the select. This is dead code but carries no runtime risk, hence 4 rather than 5.
  • src/server/routers/metrics.ts — getNodePipelineRates selects displayName but does not include it in the response

Important Files Changed

Filename Overview
src/lib/component-key.ts New utility that wraps nanoid with a 62-char alphanumeric alphabet to generate {type}_{8-char-id} keys. Alphabet correctly excludes hyphens, keeping all generated keys compatible with the existing ^[a-zA-Z_][a-zA-Z0-9_]*$ validation regex.
src/stores/flow-store.ts Replaces updateNodeKey with updateDisplayName, adopts generateComponentKey for all node-creation paths (addNode, paste, multi-paste), and threads displayName through ClipboardData and FlowNodeData. History push pattern is consistent with other store actions. computeFlowFingerprint captures displayName via the generic Object.entries filter, so renames correctly mark the pipeline dirty.
src/components/flow/detail-panel.tsx Replaces the sanitising handleKeyChange with handleNameChange (64-char trim only), swaps updateNodeKey for updateDisplayName, and renders a read-only "Component ID" below the editable "Name" input. The simplified local-state management (no more useState/useEffect for key sync) is cleaner and correct.
src/server/routers/metrics.ts getComponentMetrics correctly includes displayName in the returned components map. getNodePipelineRates adds displayName to the DB select but the field is not surfaced in the rates response — the extra column is fetched but discarded.
src/server/routers/pipeline.ts Adds displayName to the Zod nodeSchema with correct nullability, and threads it through both the save-graph upsert and the discard-to-snapshot restore. Rollback correctly passes (node.displayName as string) ?? null.
prisma/migrations/20260310000000_add_display_name_to_pipeline_node/migration.sql Correct ALTER TABLE … ADD COLUMN "displayName" TEXT with no default (nullable), matching the Prisma schema. Migration file is present and will be picked up by prisma migrate deploy on startup.

Sequence Diagram

sequenceDiagram
    participant User
    participant DetailPanel
    participant FlowStore
    participant PipelineRouter
    participant DB

    Note over User,DB: Node creation (new component key, optional display name)
    User->>FlowStore: addNode(componentDef, position)
    FlowStore->>FlowStore: generateComponentKey(type) → e.g. http_server_k7xMp2nQ
    FlowStore->>FlowStore: displayName = componentDef.displayName

    Note over User,DB: Rename flow (save only, no redeploy)
    User->>DetailPanel: types new name in "Name" input
    DetailPanel->>FlowStore: updateDisplayName(nodeId, trimmed)
    FlowStore->>FlowStore: push history snapshot, set isDirty=true
    FlowStore->>FlowStore: node.data.displayName = trimmed
    FlowStore->>FlowStore: fingerprint subscriber detects change → isDirty stays true

    User->>DetailPanel: clicks Save
    DetailPanel->>PipelineRouter: saveGraph({ nodes: [{componentKey, displayName, ...}] })
    PipelineRouter->>DB: upsert PipelineNode (componentKey immutable, displayName updated)
    DB-->>PipelineRouter: saved
    PipelineRouter-->>DetailPanel: success
    DetailPanel->>FlowStore: markClean()
    FlowStore->>FlowStore: _savedSnapshot = current fingerprint, isDirty=false

    Note over User,DB: Node label rendering
    FlowStore-->>User: node renders displayName || componentKey
Loading

Last reviewed commit: f6e3a37

Comment on lines 363 to 364
pipelineId String
pipeline Pipeline @relation(fields: [pipelineId], references: [id], onDelete: Cascade)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Prisma migration file

displayName String? was added to the PipelineNode model in the schema, but no corresponding migration was generated and committed under prisma/migrations/.

Per the project conventions, npx prisma migrate dev --name add_display_name must be run and the resulting migration file committed. Without it:

  • prisma migrate deploy (which the Docker entrypoint runs automatically on startup) finds no pending migration, so the displayName column is never created in the database.
  • Every query or upsert that touches displayName (save pipeline, rollback, copy-graph, metrics read, etc.) will fail at runtime with a database error about an unknown column.

A migration file named something like 20260310000000_add_pipeline_node_display_name containing:

ALTER TABLE "PipelineNode" ADD COLUMN "displayName" TEXT;

must be added before this is merged.

Prompt To Fix With AI
This is a comment left during a code review.
Path: prisma/schema.prisma
Line: 363-364

Comment:
**Missing Prisma migration file**

`displayName String?` was added to the `PipelineNode` model in the schema, but no corresponding migration was generated and committed under `prisma/migrations/`. 

Per the project conventions, `npx prisma migrate dev --name add_display_name` must be run and the resulting migration file committed. Without it:
- `prisma migrate deploy` (which the Docker entrypoint runs automatically on startup) finds no pending migration, so the `displayName` column is never created in the database.
- Every query or upsert that touches `displayName` (save pipeline, rollback, copy-graph, metrics read, etc.) will fail at runtime with a database error about an unknown column.

A migration file named something like `20260310000000_add_pipeline_node_display_name` containing:
```sql
ALTER TABLE "PipelineNode" ADD COLUMN "displayName" TEXT;
```
must be added before this is merged.

How can I resolve this? If you propose a fix, please make it concise.

// Map componentKey → { pipelineId, kind } using pipeline nodes
const pipelineNodes = await prisma.pipelineNode.findMany({
select: { pipelineId: true, componentKey: true, kind: true },
select: { pipelineId: true, componentKey: true, displayName: true, kind: true },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

displayName selected but not returned in response

displayName was added to the select in getNodePipelineRates, but the aggregation loop only accesses matchingNode.pipelineId and matchingNode.kind. The field is fetched from the DB and discarded. If the frontend doesn't need displayName here, the select can be reverted; if it does need it, the rates record or the return value needs to be updated to include it.

Suggested change
select: { pipelineId: true, componentKey: true, displayName: true, kind: true },
select: { pipelineId: true, componentKey: true, kind: true },
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/server/routers/metrics.ts
Line: 107

Comment:
**`displayName` selected but not returned in response**

`displayName` was added to the `select` in `getNodePipelineRates`, but the aggregation loop only accesses `matchingNode.pipelineId` and `matchingNode.kind`. The field is fetched from the DB and discarded. If the frontend doesn't need `displayName` here, the select can be reverted; if it does need it, the `rates` record or the return value needs to be updated to include it.

```suggestion
        select: { pipelineId: true, componentKey: true, kind: true },
```

How can I resolve this? If you propose a fix, please make it concise.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 10, 2026

Greptile Summary

This PR decouples pipeline component identity from user-facing labels by making componentKey an immutable, auto-generated identifier ({type}_{nanoid(8)}) and introducing an optional displayName field that users can edit freely without triggering a redeploy. The change is well-scoped — displayName is threaded consistently through save, rollback, deploy snapshots, clone/promote, templates, and metrics responses, and the canvas correctly falls back to componentKey when displayName is absent.

Issues found:

  • Missing Prisma migration fileprisma/schema.prisma adds displayName String? to PipelineNode, but no migration SQL file was committed to prisma/migrations/. The Docker entrypoint runs prisma migrate deploy on startup, which only applies committed migrations. Without it, the column will not exist in production and every tRPC call touching displayName (save, rollback, metrics, copy-graph, deploy) will fail at the database level. Run npx prisma migrate dev --name add_display_name_to_pipeline_node and commit the result.
  • Empty-string displayName breaks the canvas fallback — clearing the Name field stores "" rather than null/undefined. Because displayName ?? componentKey only falls back for null/undefined, the node shows an empty label on the canvas instead of the componentKey. The empty string is also persisted through saveGraph ("" ?? null === ""), so the broken state survives reloads.

Confidence Score: 2/5

  • Not safe to merge — the missing migration file will crash the server on startup when deployed to production.
  • The missing Prisma migration is a blocker: prisma migrate deploy runs automatically at container startup and will not create the displayName column without a committed migration file. Any code path that SELECTs or INSERTs displayName (which is now most of the pipeline CRUD surface) will throw a database error, rendering pipelines un-saveable and un-deployable. The empty-string fallback bug is minor by comparison but should also be addressed before merge.
  • prisma/schema.prisma — requires a corresponding migration file before this PR can be merged safely.

Important Files Changed

Filename Overview
prisma/schema.prisma Adds displayName String? to PipelineNode — correct schema change, but the corresponding Prisma migration file is missing from prisma/migrations/, which will prevent the column from being created in production.
src/lib/component-key.ts New utility using nanoid with a 62-character alphanumeric alphabet to generate {type}_{nanoid(8)} keys. The generated format is compatible with the existing ^[a-zA-Z_][a-zA-Z0-9_]*$ validation regex since component types always start with a letter.
src/stores/flow-store.ts Replaces updateNodeKey with updateDisplayName, switches key generation from Date.now() to nanoid(8), and threads displayName through copy/paste/duplicate paths. The fingerprinting function correctly includes displayName via its generic data-field pass-through. Minor: empty-string displayName won't fall back to componentKey on the canvas.
src/components/flow/detail-panel.tsx Replaces the Component Key input with a Name input bound to displayName and adds a read-only Component ID display. Simplifies the onChange handler by removing key sanitization, but does not guard against storing an empty-string displayName, which breaks the displayName ?? componentKey canvas fallback.
src/server/routers/pipeline.ts Adds displayName to the nodeSchema Zod validator and threads it through saveGraph upsert and rollback. Correctly handles null/undefined with ?? null on write and maps from DB on read.
src/server/routers/metrics.ts Adds displayName to getComponentMetrics and getNodePipelineRates responses by adding it to the Prisma select clause and including it in the returned component objects. Pre-existing authorization patterns are unchanged.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User adds node] --> B[generateComponentKey\ntype_nanoid8\nimmutable]
    A --> C[displayName = componentDef.displayName\neditable]

    D[User renames node] --> E[updateDisplayName\nstore action]
    E --> F[isDirty = true\nrequires save only]

    G[saveGraph tRPC] --> H[(PipelineNode DB\ncomponentKey + displayName)]

    H --> I[Deploy\ngenerateVectorYaml\nuses componentKey only]
    H --> J[Metrics matching\ncomponentId === componentKey]
    H --> K[Rollback\nrestore displayName from snapshot]

    J --> L[getComponentMetrics response\ndisplayName included for GUI labels]

    M[Canvas nodes] --> N[displayName ?? componentKey\nfallback display]
Loading

Comments Outside Diff (2)

  1. prisma/schema.prisma, line 246 (link)

    Missing Prisma migration file

    The displayName String? column is added to PipelineNode in the schema, but no corresponding migration SQL file was added to prisma/migrations/. The project's Docker entrypoint runs prisma migrate deploy automatically on startup, which applies only committed migration files — it does not auto-apply schema drift.

    Without the migration, the displayName column will never be created in production. All tRPC procedures that SELECT or INSERT displayName (pipeline save/rollback, metrics router, copy-graph, deploy snapshot) will fail at the database level.

    You need to generate and commit the migration:

    npx prisma migrate dev --name add_display_name_to_pipeline_node
    

    This creates prisma/migrations/20260310xxxxxx_add_display_name_to_pipeline_node/migration.sql with:

    ALTER TABLE "PipelineNode" ADD COLUMN "displayName" TEXT;

    Rule Used: ## Code Style & Conventions

    TypeScript Conven... (source)

  2. src/components/flow/detail-panel.tsx, line 332-333 (link)

    Empty displayName won't fall back to componentKey on canvas

    When the user clears the Name field, handleNameChange is called with raw = "". trimmed becomes "", and updateDisplayName(id, "") stores an empty string in node data. On the canvas, displayName ?? componentKey uses nullish coalescing — it only falls back for null/undefined, not for "". So a cleared name renders as an empty label instead of showing the componentKey as the fallback.

    Additionally, when this pipeline is saved and reloaded: displayName: node.displayName ?? null evaluates to "" (since "" ?? null === ""), so the empty string is persisted to DB and survives round-trips, permanently hiding the componentKey fallback.

    A simple fix is to skip the updateDisplayName call (or pass undefined) when the trimmed value is empty:

    And update updateDisplayName's type signature to accept string | undefined in the store.

Last reviewed commit: 8e4fad3

- Add migration SQL to create displayName column in production
- Use || instead of ?? for displayName fallback so empty strings
  fall back to componentKey (prevents blank node labels)
@TerrifiedBug
Copy link
Owner Author

@greptile fixed.

  1. Missing migration — Added
    prisma/migrations/20260310000000_add_display_name_to_pipeline_node/migration.sql with the ALTER TABLE to
    create the column in production.
  2. Empty string fallback — Changed displayName ?? componentKey to displayName || componentKey in all three
    node components, so clearing the Name field falls back to showing the component key instead of a blank
    label.

@TerrifiedBug TerrifiedBug merged commit a0348b5 into main Mar 10, 2026
12 checks passed
@TerrifiedBug TerrifiedBug deleted the friendly-component-ids-416 branch March 10, 2026 11:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant