feat: friendly component IDs with display names#81
Conversation
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)
Greptile SummaryThis PR decouples pipeline component identity from user-facing labels by introducing an immutable
Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
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
Last reviewed commit: f6e3a37 |
| pipelineId String | ||
| pipeline Pipeline @relation(fields: [pipelineId], references: [id], onDelete: Cascade) |
There was a problem hiding this 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 thedisplayNamecolumn 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 }, |
There was a problem hiding this 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.
| 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 SummaryThis PR decouples pipeline component identity from user-facing labels by making Issues found:
Confidence Score: 2/5
Important Files Changed
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]
|
- 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)
|
@greptile fixed.
|
Summary
{type}_{nanoid(8)}) and immutable. A newdisplayNamefield lets users rename components without redeploying.displayNameis nullable and the GUI falls back to showingcomponentKeywhen it's null.Changes
Schema: Add
displayName String?toPipelineNodeBackend: Thread
displayNamethrough save, deploy snapshots, rollback, discard, copy-pipeline-graph, templates, and metrics responsesFrontend: Replace "Component Key" input with "Name" + read-only "Component ID"; update all node components to show
displayName ?? componentKey; replaceupdateNodeKeywithupdateDisplayName; 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