Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
c0528d9
Fixing some latent type errors
ef4 Jul 22, 2026
a124880
Fix search bar types
backspace Jul 27, 2026
65127aa
Change input value to be optional
backspace Jul 27, 2026
93b79a1
Add null support for optional parameters
backspace Jul 27, 2026
db79132
Add missing underlying type
backspace Jul 27, 2026
cafcafa
Change FieldContainer label to be optional
backspace Jul 27, 2026
36ab202
Add missing input step argument
backspace Jul 27, 2026
b2bff78
Add missing acceptable pill element
backspace Jul 27, 2026
08cde7f
Fix typos
backspace Jul 27, 2026
2a7af1e
Fix button arguments
backspace Jul 27, 2026
0a055af
Remove unused icon names
backspace Jul 27, 2026
fa13caa
Remove nonexistent arguments
backspace Jul 27, 2026
1102c30
Remove non-existent argument
backspace Jul 27, 2026
39a3d74
Add missing sigils
backspace Jul 27, 2026
96ad9ce
Remove non-existent argument
backspace Jul 27, 2026
df2a1b6
Add types for some packages
backspace Jul 27, 2026
9f3c09f
Add working loaded getter for avatar
backspace Jul 27, 2026
900fdde
Fix currency editor types
backspace Jul 27, 2026
b4d3b5b
Add null-handling to validation
backspace Jul 28, 2026
a79655a
Fix interface to match actual values
backspace Jul 28, 2026
017f98e
Add missing conditional for optional icon
backspace Jul 28, 2026
67a85d0
Add handling for conditional argument
backspace Jul 28, 2026
2df5c54
Update interface to match argument shape
backspace Jul 28, 2026
f4eca1a
Update interface to match actual usage
backspace Jul 28, 2026
875ec22
Remove optionality of block parameters
backspace Jul 28, 2026
99ee22c
Fix overly-restrictive type
backspace Jul 28, 2026
c3aa9de
Change interface to match arguments
backspace Jul 28, 2026
31a861b
Change argument to optional
backspace Jul 28, 2026
4110c83
Fix change handler interfaces
backspace Jul 28, 2026
0c9802b
Fix return type
backspace Jul 28, 2026
e6b8df3
Add cast
backspace Jul 28, 2026
6a3faa5
Fix PowerSelect trigger signature
backspace Jul 28, 2026
7c61eb6
Fix handler interface
backspace Jul 28, 2026
10f768b
Add type handler
backspace Jul 28, 2026
1cf4b5e
Add cast for icons
backspace Jul 28, 2026
45c3e93
Add handling for different error shapes
backspace Jul 28, 2026
bd54255
Fix return type
backspace Jul 28, 2026
a86198c
Fix CI ordering
backspace Jul 28, 2026
58f6fcd
Add handling for missing argument
backspace Jul 28, 2026
b9b3ce3
Add signature reflection of bound arguments
backspace Jul 28, 2026
49582c4
Add fallbacks for missing argument
backspace Jul 28, 2026
da61590
Fix incorrect casts
backspace Jul 28, 2026
3951bbb
Fix filtering
backspace Jul 28, 2026
2f60125
Add missing interface match
backspace Jul 28, 2026
8b65732
Fix element type
backspace Jul 28, 2026
c6d69b9
Add narrowing for proper branch types
backspace Jul 28, 2026
5a2e2f0
Merge remote-tracking branch 'origin/main' into fixing-latent-type-er…
backspace Jul 28, 2026
f149c58
Add fallback for required value
backspace Jul 28, 2026
5bc00ce
Add script to fix declaration extensions
backspace Jul 28, 2026
d31ee30
Fix component element type
backspace Jul 28, 2026
e395466
Remove incorrect variant
backspace Jul 28, 2026
c129244
Fix parameter mismatch
backspace Jul 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions .github/workflows/ci-lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ jobs:
if: ${{ !cancelled() }}
run: pnpm run lint
working-directory: packages/boxel-ui/addon
- name: Lint Host
# Runs BEFORE Build Boxel UI so host type-checks against the Boxel UI
# source, exactly as local development does (declarations/ is never
# built locally). This also guards against declaration-emit
# regressions: an unresolvable import inside a .d.ts is silently
# suppressed under skipLibCheck and degrades every Boxel UI symbol to
# `any`, turning the host type check into a no-op (see boxel-ui's
# bin/fix-declaration-extensions.mjs).
if: ${{ !cancelled() }}
run: pnpm run lint
working-directory: packages/host
- name: Build Boxel UI
# To faciliate linting of projects that depend on Boxel UI
if: ${{ !cancelled() }}
Expand All @@ -61,10 +72,6 @@ jobs:
if: ${{ !cancelled() }}
run: pnpm run lint
working-directory: packages/boxel-ui/test-app
- name: Lint Host
if: ${{ !cancelled() }}
run: pnpm run lint
working-directory: packages/host
- name: Lint Matrix
if: ${{ !cancelled() }}
run: pnpm run lint
Expand Down
25 changes: 19 additions & 6 deletions packages/base/amount-with-currency.gts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import NumberField from './number';
import NumberField, { deserializeForUI, serializeForUI } from './number';
import { FieldDef, field, contains, Component } from './card-api';
import { NumberSerializer } from '@cardstack/runtime-common';
import { TextInputValidator } from './text-input-validator';
import CurrencyField from './currency';
import { action } from '@ember/object';
import { BoxelInputGroup } from '@cardstack/boxel-ui/components';
Expand Down Expand Up @@ -28,13 +30,23 @@ class Edit extends Component<typeof AmountWithCurrency> {
}

@action
setAmount(val: number) {
setAmount(val: number | null | undefined) {
let newModel = new AmountWithCurrency();
newModel.amount = val;
if (val != null) {
newModel.amount = val;
}
newModel.currency.code = newModel.currency.code || 'USD';
this.args.set(newModel);
}

textInputValidator: TextInputValidator<number> = new TextInputValidator(
() => this.args.model.amount ?? null,
(val) => this.setAmount(val),
deserializeForUI,
serializeForUI,
NumberSerializer.validate,
);

@action
setCurrency(val: CurrencyField) {
let newModel = new AmountWithCurrency();
Expand All @@ -47,9 +59,10 @@ class Edit extends Component<typeof AmountWithCurrency> {
<BoxelInputGroup
@id={{this.id}}
@placeholder='0.00'
@value={{@model.amount}}
@invalid={{false}}
@onInput={{this.setAmount}}
@value={{this.textInputValidator.asString}}
@state={{if this.textInputValidator.isInvalid 'invalid' 'none'}}
@errorMessage={{this.textInputValidator.errorMessage}}
@onInput={{this.textInputValidator.onInput}}
@autocomplete='off'
@inputmode='decimal'
class='input-selectable-currency-amount'
Expand Down
6 changes: 5 additions & 1 deletion packages/base/color-field/components/swatches-picker.gts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { ColorPalette } from '@cardstack/boxel-ui/components';
import type { ColorFieldSignature } from '../util/color-field-signature';

export default class SwatchesPicker extends Component<ColorFieldSignature> {
private handleChange = (value: string | null) => {
this.args.set?.(value);
};

get paletteColors() {
const options = (
this.args.configuration as ColorFieldConfiguration & {
Expand All @@ -17,7 +21,7 @@ export default class SwatchesPicker extends Component<ColorFieldSignature> {
<template>
<ColorPalette
@color={{@model}}
@onChange={{@set}}
@onChange={{this.handleChange}}
@disabled={{not @canEdit}}
@paletteColors={{this.paletteColors}}
/>
Expand Down
2 changes: 1 addition & 1 deletion packages/base/contains-many-component.gts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ class ContainsManyEditor extends GlimmerComponent<ContainsManyEditorSignature> {
{{on 'click' this.add}}
data-test-add-new
>
<IconPlus class='icon' width='12px' height='12px' alt='plus' />
<IconPlus class='icon' width='12px' height='12px' role='presentation' />
Add
{{getPlural @field.card.displayName}}
</Button>
Expand Down
6 changes: 3 additions & 3 deletions packages/base/email.gts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import MailIcon from '@cardstack/boxel-icons/mail';

class Edit extends Component<typeof EmailField> {
@action private handleChange(
value: string,
validation: EmailFormatValidationError,
value: string | null,
validation: EmailFormatValidationError | null,
) {
if (validation === null && this.args.model !== value) {
if (validation === null && value !== null && this.args.model !== value) {
this.args.set(value);
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/base/field-component.gts
Original file line number Diff line number Diff line change
Expand Up @@ -284,13 +284,13 @@ export function getBoxComponent(
return isThemeCard(cardDef) ? cardDef.id : cardDef?.cardTheme?.id;
}

function getCssImports(card?: CardDef) {
function getCssImports(card?: CardDef): string[] | undefined {
// for cards like Theme card and its descendants, directly use the `cssImports` field;
// for all other cards, get imports via the Theme card linked from cardInfo
if (card && 'cssImports' in card) {
let field = getField(card, 'cssImports');
if (field?.card?.name === 'CssImportField') {
return card.cssImports;
return card.cssImports as string[] | undefined;
}
}
return card?.cardTheme?.cssImports;
Expand Down
4 changes: 2 additions & 2 deletions packages/base/links-to-many-component.gts
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ class LinksToManyStandardEditor extends GlimmerComponent<LinksToManyStandardEdit
{{on 'click' @add}}
data-test-add-new={{@field.name}}
>
<IconPlus class='icon' width='12px' height='12px' alt='plus' />
<IconPlus class='icon' width='12px' height='12px' role='presentation' />
Add
{{getPlural @field.card.displayName}}
</Button>
Expand Down Expand Up @@ -513,7 +513,7 @@ class LinksToManyCompactEditor extends GlimmerComponent<LinksToManyCompactEditor
{{on 'click' @add}}
data-test-add-new={{@field.name}}
>
<IconPlus class='icon' width='12px' height='12px' alt='plus' />
<IconPlus class='icon' width='12px' height='12px' role='presentation' />
Add
{{@field.card.displayName}}
</Button>
Expand Down
5 changes: 4 additions & 1 deletion packages/base/process-card.gts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ class ProcessTemplate extends Component<typeof ProcessCard> {
</span>
<h3 class='process-card__name'><@fields.cardTitle /></h3>
</header>
<ProgressBar @value={{@model.percentComplete}} @max={{100}} />
<ProgressBar
@value={{if @model.percentComplete @model.percentComplete 0}}
@max={{100}}
/>
<footer class='process-card__footer'>
<span class='process-card__count'>{{@model.progressLabel}}</span>
<span
Expand Down
4 changes: 2 additions & 2 deletions packages/base/realm.gts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ type RealmMeta = {
realmIdentifier: string;
canWrite: boolean;
info: {
name?: string;
iconURL?: string;
name: string;
iconURL: string | null;
};
};

Expand Down
10 changes: 5 additions & 5 deletions packages/base/skill-reference.gts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { on } from '@ember/modifier';
import ExternalLink from '@cardstack/boxel-icons/external-link';

import { Button, Pill } from '@cardstack/boxel-ui/components';
import { cn, eq } from '@cardstack/boxel-ui/helpers';
import { eq } from '@cardstack/boxel-ui/helpers';

import { FieldDef, field, contains, linksTo, Component } from './card-api';
import enumField from './enum';
Expand Down Expand Up @@ -86,10 +86,10 @@ export class SkillReference extends FieldDef {
</h4>
<Pill
class='skill-ref-mode boxel-ellipsize'
@variant={{cn
primary=(eq @model.inclusionMode 'full')
accent=(eq @model.inclusionMode 'essential')
muted=(eq @model.inclusionMode 'link-only')
@variant={{if
(eq @model.inclusionMode 'full')
'primary'
(if (eq @model.inclusionMode 'essential') 'accent' 'muted')
}}
>
{{if
Expand Down
4 changes: 3 additions & 1 deletion packages/base/spec.gts
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,9 @@ export class SpecModuleSection extends GlimmerComponent<SpecModuleSectionSignatu
<div class='code-ref-container'>
<FieldContainer @label='URL' @vertical={{true}} @labelFontSize='small'>
<div class='code-ref-row'>
<RealmIcon class='realm-icon' @realmInfo={{this.realmInfo}} />
{{#if this.realmInfo}}
<RealmIcon class='realm-icon' @realmInfo={{this.realmInfo}} />
{{/if}}
<span class='code-ref-value' data-test-module-href>
{{this.moduleHref}}
</span>
Expand Down
47 changes: 47 additions & 0 deletions packages/boxel-ui/addon/bin/fix-declaration-extensions.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Rewrites relative `.gts` import specifiers in the emitted declaration
// files to `.js`, which consumers' module resolution maps to the sibling
// `.d.ts` files. ember-tsc emits declaration imports with their authored
// `.gts` extensions, which don't resolve from within declarations/ — and an
// unresolvable import inside a `.d.ts` is silently suppressed under
// skipLibCheck, degrading every symbol imported from this package to `any`.
import { readdir, readFile, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';

const declarationsDir = fileURLToPath(
new URL('../declarations', import.meta.url),
);

async function* dtsFiles(dir) {
for (const entry of await readdir(dir, { withFileTypes: true })) {
const path = join(dir, entry.name);
if (entry.isDirectory()) {
yield* dtsFiles(path);
} else if (entry.name.endsWith('.d.ts')) {
yield path;
}
}
}

const relativeGtsSpecifier = /(['"])(\.\.?\/[^'"]+)\.gts(\1)/g;
const anyGtsSpecifier = /['"][^'"]+\.gts['"]/;

let rewrittenFileCount = 0;
for await (const path of dtsFiles(declarationsDir)) {
const source = await readFile(path, 'utf8');
const updated = source.replace(relativeGtsSpecifier, '$1$2.js$3');
if (updated !== source) {
await writeFile(path, updated);
rewrittenFileCount++;
}
const leftover = updated.match(anyGtsSpecifier);
if (leftover) {
throw new Error(
`fix-declaration-extensions: unrewritable .gts specifier ${leftover[0]} remains in ${path}`,
);
}
}

console.log(
`fix-declaration-extensions: rewrote .gts specifiers in ${rewrittenFileCount} declaration files`,
);
2 changes: 1 addition & 1 deletion packages/boxel-ui/addon/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
"scripts": {
"build": "concurrently \"pnpm:build:*\" --names \"build:\"",
"build:types": "ember-tsc --declaration --emitDeclarationOnly --noEmit false",
"build:types": "ember-tsc --declaration --emitDeclarationOnly --noEmit false && node bin/fix-declaration-extensions.mjs",
"lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\"",
"lint:fix": "concurrently \"pnpm:lint:*:fix\" --names \"fix:\"",
"lint:hbs": "ember-template-lint .",
Expand Down
2 changes: 1 addition & 1 deletion packages/boxel-ui/addon/src/components/alert/index.gts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Button, { type BoxelButtonKind } from '../button/index.gts';

interface MessagesSignature {
Args: {
messages: string[];
messages: (string | undefined)[];
type?: 'error' | 'warning';
};
Element: HTMLDivElement;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface Signature {
description?: string;
iconComponent?: Icon;
isEmpty?: boolean;
primary: string;
primary?: string;
secondary?: string;
thumbnailURL?: string;
};
Expand Down
2 changes: 1 addition & 1 deletion packages/boxel-ui/addon/src/components/button/index.gts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ interface Signature {
as?: string;
class?: string;
disabled?: boolean;
href?: string;
href?: string | null;
kind?: BoxelButtonKind;
loading?: boolean;
models?: any;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Component from '@glimmer/component';
import IconCircle from '../../icons/icon-circle.gts';

interface Signature {
Element: SVGElement;
Element: SVGSVGElement;
}

// eslint-disable-next-line ember/no-empty-glimmer-component-classes
Expand Down
5 changes: 4 additions & 1 deletion packages/boxel-ui/addon/src/components/copy-button/index.gts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface Signature {
offset?: number;
placement?: MiddlewareState['placement'];
size?: BoxelButtonSize;
textToCopy: string;
textToCopy: string | null | undefined;
tooltipText?: string;
variant?: BoxelButtonKind;
width?: string;
Expand All @@ -31,6 +31,9 @@ export default class CopyButton extends Component<Signature> {
@tracked private recentlyCopied = false;

@action private async copyToClipboard() {
if (this.args.textToCopy == null) {
return;
}
try {
await navigator.clipboard.writeText(this.args.textToCopy);
this.recentlyCopied = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ export class DndColumn {
export interface DndKanbanBoardSignature<DndColumn> {
Args: DndKanbanBoardArgs<DndColumn>;
Blocks: {
card: [card?: DndItem, column?: DndColumn];
card: [card: DndItem, column: DndColumn];
// We yield the card and column back to the consumer so they can decide how to render it or use additional information
// This rendering by the block will typically occur at the card of the kanaban
// but with more sophistication you can use it somewhere else
header: [column?: DndColumn];
header: [column: DndColumn];
};
}

Expand Down
7 changes: 4 additions & 3 deletions packages/boxel-ui/addon/src/components/dropdown/index.gts
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ interface Signature {
Args: {
Positional: unknown[];
};
// note: should only be used with Button, but HTMLAnchorElement is included so that the
// trigger bindings can be applied to BoxelButton without glint error
Element: HTMLButtonElement | HTMLAnchorElement;
// note: the trigger should be an interactive element (Button or a
// component like Pill that renders one), but that isn't expressible
// here, so any element is accepted
Element: HTMLElement;
}>,
];
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ interface Signature {
required?: boolean;
value: string | null;
};
Element: HTMLElement;
Element: HTMLInputElement;
}

const DEFAULT_FALLBACK_MESSAGE = 'Enter a valid email address';
Expand Down
18 changes: 10 additions & 8 deletions packages/boxel-ui/addon/src/components/field-container/index.gts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface Signature {
iconHeight?: string;
iconWidth?: string;
inline?: boolean;
label: string;
label?: string;
labelFontSize?: BoxelLabelFontSize;
tag?: keyof HTMLElementTagNameMap;
vertical?: boolean;
Expand Down Expand Up @@ -52,13 +52,15 @@ const FieldContainer: TemplateOnlyComponent<Signature> = <template>
role='presentation'
/>
{{/if}}
<Label
class='label'
@size={{@labelFontSize}}
data-test-boxel-field-label
>
{{@label}}
</Label>
{{#if @label}}
<Label
class='label'
@size={{@labelFontSize}}
data-test-boxel-field-label
>
{{@label}}
</Label>
{{/if}}
{{yield to='label'}}
</div>
<div class='content'>
Expand Down
Loading
Loading