Skip to content

feat(core): add label seam (getLabelFieldName / getItemLabel + ui.labelField)#635

Merged
borisno2 merged 1 commit into
mainfrom
claude/issue-629-pfkirw
Jul 10, 2026
Merged

feat(core): add label seam (getLabelFieldName / getItemLabel + ui.labelField)#635
borisno2 merged 1 commit into
mainfrom
claude/issue-629-pfkirw

Conversation

@borisno2

Copy link
Copy Markdown
Member

Summary

  • Add ui.labelField?: string to ListUIConfig — the field used to represent a row as a single label (relationship cells, dropdown options, page headings)
  • Add getLabelFieldName(listConfig), which resolves ui.labelField ?? 'name' ?? 'title' ?? 'id' (first field that exists on the list), throwing if a configured labelField doesn't reference a declared, non-relationship field
  • Add getItemLabel(listConfig, item), which reads the field getLabelFieldName resolves off a row, falling back to id when that field is missing (e.g. stripped by field-level access)
  • Both share one resolver so the field chosen for projection can never drift from the field used for rendering
  • Export both from the @opensaas/stack-core root entry point
  • Record the Label field / Item label vocabulary in CONTEXT.md
  • No consumer changes in this slice — this is the foundation for downstream admin UI work

Test plan

  • Unit tests table over (ui.labelField, fields present, row), asserting the field getLabelFieldName returns is the field getItemLabel reads
  • pnpm test (packages/core) — all 744 tests pass
  • pnpm build (packages/core) — type-checks clean
  • pnpm lint — no new warnings/errors
  • pnpm format / pnpm manypkg fix
  • Changeset added (minor: new config option + exports)

Closes #629


Generated by Claude Code

…elField)

Adds a single resolver the admin UI can use to answer "what field
represents a row, and what text is its label" — relationship cells,
dropdown options, and page headings all read from the same place, so
projection and rendering can't drift apart.
@changeset-bot

changeset-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 41e4352

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@opensaas/stack-core Minor
@opensaas/stack-auth Minor
@opensaas/stack-cli Minor
@opensaas/stack-rag Minor
@opensaas/stack-storage Minor
@opensaas/stack-tiptap Minor
@opensaas/stack-ui Minor
@opensaas/stack-storage-s3 Minor
@opensaas/stack-storage-vercel Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stack-docs Ready Ready Preview, Comment Jul 10, 2026 6:44am

Copy link
Copy Markdown
Member Author

Review: Label seam (getLabelFieldName / getItemLabel + ui.labelField)

Overview

Adds a single label-resolution seam to stack-core: getLabelFieldName(listConfig) resolves which field represents a row (ui.labelFieldnametitleid), and getItemLabel(listConfig, item) reads that same field off a row, falling back to id when it's absent. Both are exported from the root entry point, ui.labelField is added to ListUIConfig, and the "Label field" / "Item label" vocabulary is recorded in CONTEXT.md. Pure addition — no consumer wiring in this slice, matching the issue's stated scope.

Code quality & style

  • Implementation is small, well-contained, and matches the codebase's ListConfig<any> + eslint-disable-next-line @typescript-eslint/no-explicit-any convention used throughout context/index.ts / write-pipeline.ts.
  • getItemLabel correctly delegates to getLabelFieldName rather than re-deriving the fallback order, which is exactly the guarantee the issue asks for (projection and render can't drift).
  • Object.prototype.hasOwnProperty.call(item, fieldName) is the right way to distinguish "field stripped from row" from "field present but falsy" — more precise than an in check or truthiness check.
  • CONTEXT.md entry follows the existing term/definition/_Avoid_ format used by every other entry in the file.
  • JSDoc on getLabelFieldName/getItemLabel and the ui.labelField type is clear and includes a usage example, consistent with the rest of types.ts.

Test coverage

  • Good table-driven coverage: fallback order (labelField → name → title → id), both throw paths (missing field, relationship field), null vs. missing field-on-row, and a dedicated it.each asserting the projection⇄render agreement across 5 scenarios — this directly satisfies the issue's acceptance criterion for that test shape.
  • All 744 tests pass; pnpm build, pnpm lint, pnpm format are clean.

Potential issues / minor suggestions (non-blocking)

  1. System fields (id, createdAt, updatedAt) can't be explicitly set as ui.labelField. Since these are auto-injected by the generator rather than present in listConfig.fields, ui: { labelField: 'createdAt' } would throw "does not reference a field declared on this list" even though createdAt is a perfectly valid, always-present scalar on every row. id is already the terminal fallback so that specific case is low-impact, but createdAt/updatedAt are plausible label choices (e.g. "show record by creation date") that this rejects. Worth a follow-up if that's a use case downstream slices care about — not a blocker for this foundational slice.
  2. Per-call resolution in getItemLabel. Each call re-derives getLabelFieldName (an in check plus optional throw), which is cheap in isolation but will run once per row when list views wire this up later. Not an issue at this slice's scope (no consumers yet), just worth keeping in mind if a future list-view render loop calls it per-row per-render — memoizing the resolved field name per list would be a trivial follow-up if it shows up in profiling.
  3. Minor: the error thrown when ui.labelField is misconfigured is a plain Error, matching the existing plugin-engine.ts convention (no custom error class in this codebase for config-shape errors), so this is consistent rather than a gap.

Security

No security-relevant surface here — pure config resolution over already-trusted config/row data, no user input parsing, no injection surface.

Verdict

Solid, narrowly-scoped foundational change. No correctness bugs found; the two notes above are forward-looking observations for when this seam gets wired into the admin UI, not defects in this PR.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Core Package Coverage (./packages/core)

Status Category Percentage Covered / Total
🔵 Lines 92.11% (🎯 65%) 888 / 964
🔵 Statements 91.42% (🎯 65%) 928 / 1015
🔵 Functions 98% (🎯 62%) 147 / 150
🔵 Branches 80.64% (🎯 50%) 604 / 749
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/core/src/config/label.ts 100% 100% 100% 100%
Generated in workflow #1228 for commit 41e4352 by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for UI Package Coverage (./packages/ui)

Status Category Percentage Covered / Total
🔵 Lines 76.03% 92 / 121
🔵 Statements 75.39% 95 / 126
🔵 Functions 75.6% 31 / 41
🔵 Branches 65.78% 75 / 114
File CoverageNo changed files found.
Generated in workflow #1228 for commit 41e4352 by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for CLI Package Coverage (./packages/cli)

Status Category Percentage Covered / Total
🔵 Lines 79.21% 1490 / 1881
🔵 Statements 78.92% 1550 / 1964
🔵 Functions 84.45% 201 / 238
🔵 Branches 67.25% 653 / 971
File CoverageNo changed files found.
Generated in workflow #1228 for commit 41e4352 by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Auth Package Coverage (./packages/auth)

Status Category Percentage Covered / Total
🔵 Lines 74.64% 159 / 213
🔵 Statements 69.74% 166 / 238
🔵 Functions 83.11% 64 / 77
🔵 Branches 70.67% 94 / 133
File CoverageNo changed files found.
Generated in workflow #1228 for commit 41e4352 by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Storage Package Coverage (./packages/storage)

Status Category Percentage Covered / Total
🔵 Lines 74.8% 190 / 254
🔵 Statements 76.44% 211 / 276
🔵 Functions 85.89% 67 / 78
🔵 Branches 70.73% 174 / 246
File CoverageNo changed files found.
Generated in workflow #1228 for commit 41e4352 by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for RAG Package Coverage (./packages/rag)

Status Category Percentage Covered / Total
🔵 Lines 47.97% 355 / 740
🔵 Statements 48.14% 377 / 783
🔵 Functions 54.26% 70 / 129
🔵 Branches 42.55% 180 / 423
File CoverageNo changed files found.
Generated in workflow #1228 for commit 41e4352 by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Storage S3 Package Coverage (./packages/storage-s3)

Status Category Percentage Covered / Total
🔵 Lines 100% 40 / 40
🔵 Statements 100% 40 / 40
🔵 Functions 100% 9 / 9
🔵 Branches 100% 19 / 19
File CoverageNo changed files found.
Generated in workflow #1228 for commit 41e4352 by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Storage Vercel Package Coverage (./packages/storage-vercel)

Status Category Percentage Covered / Total
🔵 Lines 100% 38 / 38
🔵 Statements 100% 38 / 38
🔵 Functions 100% 8 / 8
🔵 Branches 100% 22 / 22
File CoverageNo changed files found.
Generated in workflow #1228 for commit 41e4352 by the Vitest Coverage Report Action

@borisno2 borisno2 merged commit 18c39c8 into main Jul 10, 2026
6 checks passed
@borisno2 borisno2 deleted the claude/issue-629-pfkirw branch July 10, 2026 07:31
@github-actions github-actions Bot mentioned this pull request Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Label seam in stack-core (getLabelFieldName / getItemLabel + ui.labelField)

2 participants