feat: derive element technology from icon name#2642
Conversation
Automatically infer a human-readable technology label from built-in icon names (aws:, azure:, gcp:, tech:) when technology is not explicitly set. Configurable via `inferTechnologyFromIcon` in project config (default: true). Co-Authored-By: Claude Opus 4.6 <[email protected]>
🦋 Changeset detectedLatest commit: b5de316 The changes in this PR will be included in the next version bump. This PR includes changesets to release 19 packages
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 |
Add documentation for auto-derived technology from icon names to: - Project config page (new config option section) - Model page (note in Technology section) - Styling page (note in Bundled icons section) Co-Authored-By: Claude Opus 4.6 <[email protected]>
📝 WalkthroughWalkthroughThe PR introduces automatic technology derivation from icon names. When an element's icon has a prefix (aws:, azure:, gcp:, tech:), LikeC4 automatically infers a human-readable technology label. This feature defaults to enabled but can be disabled via Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
packages/language-server/src/model/__tests__/derive-technology-from-icon.spec.ts (1)
119-176: Missing edge case: element explicit technology + kind icon.The current tests cover:
- Element tech + element icon → element tech wins (line 102)
- Kind tech + element icon → kind tech wins (line 119)
- Kind icon only → derived tech applied (line 138)
But the combination element explicit technology + kind icon (no element icon) is not tested. Depending on the priority chain in
MergedSpecification.ts, element technology should still win here, but a test would confirm this.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/language-server/src/model/__tests__/derive-technology-from-icon.spec.ts` around lines 119 - 176, Add a test case in derive-technology-from-icon.spec.ts that verifies an element with an explicit technology value wins over a kind-level icon: use createTestServices(), call validate() with a specification that sets a kind-level icon under "element component { style { icon tech:docker } }" but in the model declare "comp1 = component 'My App' { technology 'Explicit Tech' }" (no element icon), then buildModel() and assert model.elements['comp1']?.technology === 'Explicit Tech'; this mirrors existing tests using validate, buildModel and the inferTechnologyFromIcon config pattern so it fits the suite.schemas/likec4-config.schema.json (2)
82-85: Consider adding"default": trueannotation.The description text documents the default, but the machine-readable
"default"annotation is absent. IDE tooling and documentation generators use this annotation to surface the default value to users.✨ Proposed improvement
"inferTechnologyFromIcon": { "description": "Automatically derive element technology from icon name when technology is not set explicitly.\nApplies to aws:, azure:, gcp:, and tech: icons. Bootstrap icons are excluded.\nDefaults to true.", - "type": "boolean" + "type": "boolean", + "default": true }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@schemas/likec4-config.schema.json` around lines 82 - 85, The JSON Schema property "inferTechnologyFromIcon" lacks a machine-readable default; update the schema entry for inferTechnologyFromIcon in schemas/likec4-config.schema.json to include "default": true alongside the existing "description" and "type" so IDEs and docs generators can surface the default value (ensure the "default" key is a boolean true).
42-44: RedundantpropertyNamesconstraint.
"propertyNames": { "type": "string" }is always satisfied in JSON (object keys are strings by definition). It can be removed without any change in validation semantics.🧹 Proposed simplification
"metadata": { "description": "Arbitrary metadata as key-value pairs for custom project information", "type": "object", - "propertyNames": { - "type": "string" - }, "additionalProperties": {} },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@schemas/likec4-config.schema.json` around lines 42 - 44, Remove the redundant "propertyNames" constraint from the schema: the block '"propertyNames": { "type": "string" }' should be deleted because object keys are always strings in JSON and the constraint does not change validation; update the schema in the likec4-config definition by removing that "propertyNames" entry so only meaningful constraints remain.packages/language-server/src/model/builder/MergedSpecification.ts (1)
22-44: LGTM — clean implementation with good JSDoc coverage.The regex is module-level (no
gflag, soexecis stateless), thematch[2]!non-null assertion is safe (.+guarantees at least one captured character), suffix stripping and title-casing are correct, andbootstrap:exclusion works by omission from the alternation. ✓One cosmetic note: icon names containing consecutive hyphens (e.g.
tech:apache--flink) produce a double space viasplit('-')→['apache', '', 'flink']→'Apache Flink'. This is an unlikely edge case with real icon names, but could be guarded with a filter if needed:🔍 Optional guard against empty segments
- return name - .split('-') - .map(word => word.charAt(0).toUpperCase() + word.slice(1)) - .join(' ') + return name + .split('-') + .filter(word => word.length > 0) + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' ')🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/language-server/src/model/builder/MergedSpecification.ts` around lines 22 - 44, The current deriveTechnologyFromIcon function can produce double spaces when icon names contain consecutive hyphens (e.g., "tech:apache--flink"); update deriveTechnologyFromIcon to filter out empty segments after splitting (use a filter like Boolean or s => s.length > 0) before title-casing and joining, so the code around iconTechPrefixRe.exec(icon), the name = match[2]!.replace(/-icon$/, '') and the subsequent name.split('-').map(...).join(' ') sequence will produce clean single-spaced results even for malformed inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.changeset/derive-tech-from-icon.md:
- Line 9: The sentence fragment "Can be disabled via `inferTechnologyFromIcon:
false` in project config." needs a subject—update the prose in
.changeset/derive-tech-from-icon.md so it reads with a clear subject, e.g. "This
can be disabled via `inferTechnologyFromIcon: false` in project config." or
"Inference can be disabled via `inferTechnologyFromIcon: false` in project
config.", leaving the `inferTechnologyFromIcon: false` token and the example
`tech:apache-flink` text unchanged.
In
`@packages/language-server/src/model/__tests__/derive-technology-from-icon.spec.ts`:
- Around line 40-43: The test description incorrectly mentions "null" even
though deriveTechnologyFromIcon accepts only string | undefined; update the it()
description in derive-technology-from-icon.spec.ts to say it returns undefined
for undefined/empty input so it accurately reflects the tested cases (keep the
existing expectations calling deriveTechnologyFromIcon(undefined) and
deriveTechnologyFromIcon('') unchanged).
In `@packages/language-server/src/model/builder/buildModel.ts`:
- Around line 346-347: The model's inferTechnologyFromIcon field is omitted when
project.config.inferTechnologyFromIcon is undefined; normalize it here by
setting inferTechnologyFromIcon: project.config.inferTechnologyFromIcon ?? true
when building the model so the serialized model always contains the effective
boolean. Update the construction that currently passes inferTechnologyFromIcon
(and is wrapped by exact()) in buildModel.ts so it mirrors the normalization
already done before creating MergedSpecification, ensuring downstream consumers
(renderers/exporters) receive the resolved value.
---
Nitpick comments:
In
`@packages/language-server/src/model/__tests__/derive-technology-from-icon.spec.ts`:
- Around line 119-176: Add a test case in derive-technology-from-icon.spec.ts
that verifies an element with an explicit technology value wins over a
kind-level icon: use createTestServices(), call validate() with a specification
that sets a kind-level icon under "element component { style { icon tech:docker
} }" but in the model declare "comp1 = component 'My App' { technology 'Explicit
Tech' }" (no element icon), then buildModel() and assert
model.elements['comp1']?.technology === 'Explicit Tech'; this mirrors existing
tests using validate, buildModel and the inferTechnologyFromIcon config pattern
so it fits the suite.
In `@packages/language-server/src/model/builder/MergedSpecification.ts`:
- Around line 22-44: The current deriveTechnologyFromIcon function can produce
double spaces when icon names contain consecutive hyphens (e.g.,
"tech:apache--flink"); update deriveTechnologyFromIcon to filter out empty
segments after splitting (use a filter like Boolean or s => s.length > 0) before
title-casing and joining, so the code around iconTechPrefixRe.exec(icon), the
name = match[2]!.replace(/-icon$/, '') and the subsequent
name.split('-').map(...).join(' ') sequence will produce clean single-spaced
results even for malformed inputs.
In `@schemas/likec4-config.schema.json`:
- Around line 82-85: The JSON Schema property "inferTechnologyFromIcon" lacks a
machine-readable default; update the schema entry for inferTechnologyFromIcon in
schemas/likec4-config.schema.json to include "default": true alongside the
existing "description" and "type" so IDEs and docs generators can surface the
default value (ensure the "default" key is a boolean true).
- Around line 42-44: Remove the redundant "propertyNames" constraint from the
schema: the block '"propertyNames": { "type": "string" }' should be deleted
because object keys are always strings in JSON and the constraint does not
change validation; update the schema in the likec4-config definition by removing
that "propertyNames" entry so only meaningful constraints remain.
|
|
||
| Automatically derive element technology from icon name when not set explicitly. | ||
| Elements with `aws:`, `azure:`, `gcp:`, or `tech:` icons will get a human-readable technology label | ||
| (e.g. `tech:apache-flink` → "Apache Flink"). Can be disabled via `inferTechnologyFromIcon: false` in project config. |
There was a problem hiding this comment.
Minor prose: sentence fragment.
"Can be disabled via..." lacks a subject.
✏️ Suggested fix
-(e.g. `tech:apache-flink` → "Apache Flink"). Can be disabled via `inferTechnologyFromIcon: false` in project config.
+(e.g. `tech:apache-flink` → "Apache Flink"). It can be disabled via `inferTechnologyFromIcon: false` in project config.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| (e.g. `tech:apache-flink` → "Apache Flink"). Can be disabled via `inferTechnologyFromIcon: false` in project config. | |
| (e.g. `tech:apache-flink` → "Apache Flink"). It can be disabled via `inferTechnologyFromIcon: false` in project config. |
🧰 Tools
🪛 LanguageTool
[style] ~9-~9: To form a complete sentence, be sure to include a subject.
Context: ... tech:apache-flink → "Apache Flink"). Can be disabled via `inferTechnologyFromIco...
(MISSING_IT_THERE)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.changeset/derive-tech-from-icon.md at line 9, The sentence fragment "Can be
disabled via `inferTechnologyFromIcon: false` in project config." needs a
subject—update the prose in .changeset/derive-tech-from-icon.md so it reads with
a clear subject, e.g. "This can be disabled via `inferTechnologyFromIcon: false`
in project config." or "Inference can be disabled via `inferTechnologyFromIcon:
false` in project config.", leaving the `inferTechnologyFromIcon: false` token
and the example `tech:apache-flink` text unchanged.
| it('returns undefined for undefined/null/empty input', () => { | ||
| expect(deriveTechnologyFromIcon(undefined)).toBeUndefined() | ||
| expect(deriveTechnologyFromIcon('')).toBeUndefined() | ||
| }) |
There was a problem hiding this comment.
Test description inaccurately mentions "null".
The function signature is (icon: string | undefined), so null is not a valid input and isn't tested. The description should only reference undefined and empty string.
✏️ Suggested rename
- it('returns undefined for undefined/null/empty input', () => {
+ it('returns undefined for undefined or empty input', () => {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| it('returns undefined for undefined/null/empty input', () => { | |
| expect(deriveTechnologyFromIcon(undefined)).toBeUndefined() | |
| expect(deriveTechnologyFromIcon('')).toBeUndefined() | |
| }) | |
| it('returns undefined for undefined or empty input', () => { | |
| expect(deriveTechnologyFromIcon(undefined)).toBeUndefined() | |
| expect(deriveTechnologyFromIcon('')).toBeUndefined() | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/language-server/src/model/__tests__/derive-technology-from-icon.spec.ts`
around lines 40 - 43, The test description incorrectly mentions "null" even
though deriveTechnologyFromIcon accepts only string | undefined; update the it()
description in derive-technology-from-icon.spec.ts to say it returns undefined
for undefined/empty input so it accurately reflects the tested cases (keep the
existing expectations calling deriveTechnologyFromIcon(undefined) and
deriveTechnologyFromIcon('') unchanged).
| inferTechnologyFromIcon: project.config.inferTechnologyFromIcon, | ||
| }), |
There was a problem hiding this comment.
inferTechnologyFromIcon in model data is unnormalized — absent means true.
When project.config.inferTechnologyFromIcon is undefined, exact() strips the key, so the serialized model omits it entirely. Consumers must apply their own default (undefined → true) to recover the effective value. Line 72 explicitly normalises via ?? true before passing to MergedSpecification, but the model data does not.
If downstream consumers (e.g., renderers, exporters) need the resolved value, consider normalising here too:
🔧 Optional: expose the effective value
- inferTechnologyFromIcon: project.config.inferTechnologyFromIcon,
+ inferTechnologyFromIcon: project.config.inferTechnologyFromIcon ?? true,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| inferTechnologyFromIcon: project.config.inferTechnologyFromIcon, | |
| }), | |
| inferTechnologyFromIcon: project.config.inferTechnologyFromIcon ?? true, | |
| }), |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/language-server/src/model/builder/buildModel.ts` around lines 346 -
347, The model's inferTechnologyFromIcon field is omitted when
project.config.inferTechnologyFromIcon is undefined; normalize it here by
setting inferTechnologyFromIcon: project.config.inferTechnologyFromIcon ?? true
when building the model so the serialized model always contains the effective
boolean. Update the construction that currently passes inferTechnologyFromIcon
(and is wrapped by exact()) in buildModel.ts so it mirrors the normalization
already done before creating MergedSpecification, ensuring downstream consumers
(renderers/exporters) receive the resolved value.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/language-server/src/model/builder/MergedSpecification.ts (1)
35-44:deriveTechnologyFromIconcan return""for degenerate inputs.For an icon like
tech:-icon,match[2]="-icon", and after.replace(/-icon$/, '')the name is"". The function then returns""(empty string) rather thanundefined. IntoModelElement, that empty string is assigned totechnology, and sinceexact()does not strip empty strings, the element would carrytechnology: ""in the built model — potentially rendering an empty technology label in the UI.♻️ Proposed guard
- return name - .split('-') - .map(word => word.charAt(0).toUpperCase() + word.slice(1)) - .join(' ') + const label = name + .split('-') + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' ') + return label || undefined🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/language-server/src/model/builder/MergedSpecification.ts` around lines 35 - 44, deriveTechnologyFromIcon currently returns an empty string for inputs like "tech:-icon" because match[2] becomes "-icon" and replace yields "", which results in an empty technology assigned downstream; update deriveTechnologyFromIcon to check the cleaned name after const name = match[2]!.replace(/-icon$/, '') and return undefined if name is falsy (e.g., if (!name) return undefined) so callers like toModelElement never receive an empty string for technology.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In @.changeset/derive-tech-from-icon.md:
- Line 9: The sentence fragment lacks a subject; update the line "(e.g.
`tech:apache-flink` → "Apache Flink"). Can be disabled via
`inferTechnologyFromIcon: false` in project config." to include a clear subject
such as "This behavior" or "Technology inference" (e.g., "This behavior can be
disabled via `inferTechnologyFromIcon: false` in project config.") so the
sentence is complete and reads correctly.
In
`@packages/language-server/src/model/__tests__/derive-technology-from-icon.spec.ts`:
- Around line 40-43: Update the test description in the
derive-technology-from-icon.spec.ts test case to accurately reflect the allowed
inputs: replace the phrase "undefined/null/empty input" with "undefined or empty
input" so it matches the function signature of deriveTechnologyFromIcon(icon:
string | undefined) and the assertions calling
deriveTechnologyFromIcon(undefined) and deriveTechnologyFromIcon('').
In `@packages/language-server/src/model/builder/buildModel.ts`:
- Line 346: The serialized project payload omits inferTechnologyFromIcon when
project.config.inferTechnologyFromIcon is undefined (exact() strips the key),
causing consumers to miss the normalized default; update the serialization in
buildModel.ts to normalize this flag like the earlier normalization before
constructing MergedSpecification by emitting a boolean value (true when
undefined) for inferTechnologyFromIcon based on project.config (or the same
normalization helper used on Line 72) so the emitted object always contains a
concrete true/false for inferTechnologyFromIcon.
---
Nitpick comments:
In `@packages/language-server/src/model/builder/MergedSpecification.ts`:
- Around line 35-44: deriveTechnologyFromIcon currently returns an empty string
for inputs like "tech:-icon" because match[2] becomes "-icon" and replace yields
"", which results in an empty technology assigned downstream; update
deriveTechnologyFromIcon to check the cleaned name after const name =
match[2]!.replace(/-icon$/, '') and return undefined if name is falsy (e.g., if
(!name) return undefined) so callers like toModelElement never receive an empty
string for technology.
Summary
technologylabel from built-in icon names (aws:,azure:,gcp:,tech:) when technology is not explicitly set on the element or its kind-iconsuffix and converts kebab-case to title case (e.g.tech:apache-flink→ "Apache Flink",aws:simple-storage-service→ "Simple Storage Service")bootstrap:icons are excluded (generic UI icons)inferTechnologyFromIcon: booleanin project config (defaults totrue)Test plan
deriveTechnologyFromIconfunction (all prefixes, suffix stripping, edge cases)pnpm --filter @likec4/language-server build)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation