Skip to content

Latest commit

 

History

History
415 lines (334 loc) · 18 KB

File metadata and controls

415 lines (334 loc) · 18 KB

Agent Instructions

This repository is a monorepo managed with pnpm and Nx. It contains multiple packages under packages/ such as web components, themes, adapters, samples and tooling.

Handling hints

We have a monorepo structure with multiple packages, each with its own package.json. The root package.json contains shared dependencies and scripts. Use pnpm commands to manage dependencies and run scripts across packages.

  • To install dependencies, use pnpm i at the root level. This will install all dependencies for all packages.
  • If you change a dependency in a package:
    • Use only exact version numbers in package.json. Other peers will not be able to use the package if you can use a range version.
    • You need to run pnpm i at the root level. This updates the lockfile and ensures all packages are using the correct versions.
  • Never add a packageManager field to any package.json file.
  • Avoid that branch name may contain hidden characters.
  • If something does not work, check in the event of an error whether all dependent submodules have been built.
  • To build a single package faster, run commands with downstream dependents using pnpm --filter ...<package> (e.g., pnpm --filter ...@public-ui/sample-react build).

🚨 Format-first rule

Stop before you commit: run the formatter so CI never rejects your patch for style drift.

  1. Run pnpm format from the repo root whenever you change code, docs or configs.
  2. If you only touched one package, you may instead run pnpm --filter <package> format for a quicker pass.
  3. Re-stage the affected files (git add -u) so the formatted result is what lands in the commit.

No package scripts in this repo need extra flags such as -- --write; the scripts already know when to write changes versus just check.

Semantic Versioning

This repository follows Semantic Versioning (SemVer) for all packages. Each package version is defined in its own package.json file. The versioning scheme is as follows:

  • Major version: Incremented for incompatible API changes.
  • Minor version: Incremented for adding functionality in a backwards-compatible manner.
  • Patch version: Incremented for backwards-compatible bug fixes.

If we deprecate a feature, we will mark it as deprecated in the code and documentation, but we will not remove it immediately. Instead, we will provide a migration guide (migration*.md) for users to transition to the new feature. Also we provide a migration tool in the packages/tools/kolibri-cli package to help with the migration process. You have to add a migration task from the previous version to the new version in the packages/tools/kolibri-cli/src/migrations folder. In the migration package, are a lot of migration tasks already implemented, so you can use them as a reference.

Project Structure

  • packages/components – Stencil based web components
    • packages/components/src/component – components
    • packages/components/src/schema – schema definitions for all components
  • packages/samples – sample applications demonstrating usage
    • packages/samples/angular – Angular sample app; do not edit
    • packages/samples/react – React sample app; all samples; write component samples here
  • packages/adapters/* – generated framework integration packages; do not edit
  • packages/themes – style themes and assets
    • packages/themes/default – primary maintained standard theme
    • All other themes are not actively maintained
  • packages/tools/kolibri-cli – helper CLI for migration
  • Documentation lives in docs/.
  • Do always ignore and do not edit all assets folders in all packages, as these are generated by the build process and should not be edited manually. If you need to change something in the assets, you have to change it in the source code and rebuild the package.

Theming

The theming is realized with adopted style sheets on web components and will be adopted at the mounted hook of the components. All following styling rules are only relevant for the components and themes packages.

The 5 styling layers

  1. A11y Preset layer: This layer comes out of the adopted-style-sheets package and contains the basic styles for accessibility. It is applied to all components.
  2. Basis Global layer: This layer contains the basis global styles for all components and comes out of the @public-ui/components package. It is applied only component specific layout styles without margins and paddings. Generally, the styling works without colors, as the colors should only be set through the custom Theme Layer.
  3. Basis Component layer: This layer contains the basis styles for one component and comes out of the @public-ui/components package. It is applied only component specific layout styles without margins and paddings. Generally, the styling works without colors, as the colors should only be set through the custom Theme Layer.
  4. Theme Global layer: This layer contains the global styles for all components of a theme and comes out of a own theme package, like @public-ui/theme-default.
  5. Theme Component layer: This layer contains the component specific styles for one component of a theme and comes out of a own theme package, like @public-ui/theme-default.

Global accessibility styles

/*
 * This file contains all rules for accessibility.
 */
@layer kol-global {
	:host {
		/*
		 * Minimum size of interactive elements.
		 */
		--a11y-min-size: #{rem(44)};
		/*
		 * No element should be used without a background and font color whose contrast ratio has
		 * not been checked. By initially setting the background color to white and the font color
		 * to black, the contrast ratio is ensured and explicit adjustment is forced.
		 */
		background-color: white;
		color: black;
	}

	* {
		/*
		 * This rule enables the word dividing for all texts. That is important for high zoom levels.
		 */
		hyphens: auto;
		/*
		 * Verdana is an accessible font that can be used without requiring additional loading time.
		 */
		font-family: Verdana;
		/*
		 * Letter spacing is required for all texts.
		 */
		letter-spacing: inherit;
		/*
		 * This rule enables the word dividing for all texts. That is important for high zoom levels.
		 */
		word-break: break-word;
		/*
		 * Word spacing is required for all texts.
		 */
		word-spacing: inherit;
	}

	/*
	 * All interactive elements should have a minimum size of rem(44).
	 */
	/* input:not([type='checkbox'], [type='radio'], [type='range']), */
	/* option, */
	/* select, */
	/* textarea, */
	[role='button'],
	button:not([role='link']),
	.kol-input .input {
		min-height: var(--a11y-min-size);
		min-width: var(--a11y-min-size);
	}

	/*
	 * Some interactive elements should not inherit the font-family and font-size.
	 */
	a,
	button,
	h1,
	h2,
	h3,
	h4,
	h5,
	h6,
	input,
	option,
	select,
	textarea {
		/*
		 * All elements should inherit the font family from his parent element.
		 */
		font-family: inherit;
		/*
		 * All elements should inherit the font size from his parent element.
		 */
		font-size: inherit;
	}
}

/**
 * Sometimes we need the semantic element for accessibility reasons,
 * but we don't want to show it.
 *
 * - https://www.a11yproject.com/posts/how-to-hide-content/
 */
.visually-hidden {
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	height: 1px;
	overflow: hidden;
	position: absolute;
	white-space: nowrap;
	width: 1px;
}

Styling rules

The basis global layer set the default font-size and box-sizing for all components.

@layer kol-global {
	:host {
		font-size: rem(16);
		/*
		 * The max-width is needed to prevent the table from overflowing the
		 * parent node, if the table is wider than the parent node.
		 */
		max-width: 100%;
	}

	* {
		/*
		 * We prefer to box-sizing: border-box for all elements.
		 */
		box-sizing: border-box;
	}
  ...
}

Custom Theming rules

The custom theme layer is used to set the colors and other theme specific styles. The custom theme layer should not contain any layout styles, as these are already set in the basis global and component layers.

For example, generally the font-family is set in the theme global layer, on the :host element, so that all components inherit the font-family from the theme. The font-size is set in the basis global layer, so that all components inherit the font-size from the basis global layer. But it is possible to set a other base font-size in the theme global layer, if needed.

@layer kol-theme-global {
	:host {
		--font-family: var(--kolibri-font-family, Verdana, Arial, Calibri, Helvetica, sans-serif);
		--font-size: var(--kolibri-font-size, #{rem(16)});
    ...
	}

	:host {
		font-size: var(--font-size);

		* {
			font-family: var(--font-family);
		}
	}
  ...
}

In the theme component layer, you can set what ever you need to realize your own custom style guidelines. For example, you can set the colors, borders, shadows, etc. for the component.

@layer kol-theme-component {
  ...
}

CSS Custom Properties and SASS Variables

CSS custom properties remain part of the global cascade and are not isolated by the Shadow DOM. Overusing them in theme files can collide with variables defined on a host page. Expose only well‑prefixed design tokens as custom properties and rely on SASS variables for internal calculations to keep components robust and avoid unintended style leaks.

SCSS Architecture Guidelines: BEM with Smart Nesting

When writing component theme styles, follow these principles for clean, maintainable SCSS:

1. Flat BEM Structure (Root Level)

All BEM modifiers and independent elements belong at root level of the mixin. Never nest BEM class names:

@mixin kol-alert-theme {
	// ✅ All BEM selectors on root (flat)
	.kol-alert { ... }
	.kol-alert--variant-msg { ... }
	.kol-alert--variant-card { ... }
	.kol-alert--type-default { ... }
	.kol-alert__container { ... }
	.kol-alert__heading { ... }
}

Why? BEM classnames are independent selectors. Each class should be predictable and flat.

2. Contextual Nesting (When Variants Change Element Behavior)

Nest only when a modifier changes how a child element behaves. Keep all rules for an element together:

.kol-alert__closer {
	place-self: center;

	.kol-button {
		border-radius: 50%;
		width: var(--a11y-min-size);
		height: var(--a11y-min-size);
		cursor: pointer;

		// ✅ Variant changes button styling - nest here
		.kol-alert--variant-msg & {
			--text-color: var(--alert-accent-color);
		}

		.kol-alert--variant-card & {
			--text-color: var(--color-light);
		}
	}
}

Rule of thumb: All rules for .kol-button in .kol-alert__closer stay in one place. This keeps related styles together and makes maintenance easier.

3. Never Use $root or @at-root

Outdated pattern (removed):

.kol-alert {
	$root: &;
	&__closer {
		@at-root #{$root}--variant-msg & { ... }
	}
}

Modern pattern (direct nesting):

.kol-alert__closer {
	.kol-button {
		.kol-alert--variant-msg & { ... }
	}
}

This is clearer and doesn't require Sass variable gymnastics.

4. Structure Template

@mixin kol-component-theme {
	// 1. Base component (no children)
	.kol-component {
		display: flex;
		width: 100%;
	}

	// 2. All BEM modifiers at root (flat)
	.kol-component--variant-a {
		...
	}

	.kol-component--type-default {
		...
	}

	// 3. Elements with contextual nesting for variants
	.kol-component__heading {
		font-weight: bold;

		.kol-component--variant-a & {
			color: red;
		}
	}

	.kol-component__content {
		padding: 1rem;

		.kol-component--variant-a & {
			background: white;
		}
	}

	// 4. Child components with variant context
	.kol-component__button {
		.kol-button {
			cursor: pointer;

			.kol-component--variant-a & {
				--text-color: var(--accent);
			}
		}
	}
}

General rules for custom themes

  • Do not use !important in your styles, as this will override the styles of the basis global and component layers.
  • Do only overwrite styling definitions you will really customize. Do not set styling definitions that are already set (redundant) in the basis global and component layers, as this will override the styles of the basis global and component layers.
  • Do not inherit styles over the :host element, as this will override the styles of the basis global and component layers. This makes your component less robust from outside environment styles. Only the kol-icon inherits some specific styles, like color, font-size, font-family and line-height, as these are needed for the icon to be displayed correctly inline to this neighbored elements.
  • Do not set the default font-family, font-size or box-sizing in the basis or theme component layer (redundant), as these are already set in the basis global layers. If you need to set a different font-family or font-size, you can do this in the theme global layer.
  • Do not set margin or padding in the basis global and component layers. If you need to set a different margin or padding, you can do this in the theme global or component layers.
  • Do not use overflow: hidden in styling or theming, as it often causes issues for reuse and should be avoided.
  • Do not use @layer declarations in utility files: Helper files, mixin files, and partial files (starting with _) should not contain @layer declarations. These files are utilities and should be layer-agnostic. This is enforced by the custom Stylelint rule kolibri/no-layer-in-utility-files.
  • Never use $root variables or @at-root in component mixins: All selectors should be explicit. Use direct child/descendant nesting only when a modifier changes element behavior within a specific context.

Samples

The samples are located in packages/samples/react and demonstrate how to use the components in react. Each component has its own folder and the basic sample are in basic.tsx. Other stories can be added in the same folder. All samples of a component are registered in the routes.ts file.

Coding Conventions

  • Formatting is enforced via Prettier with settings defined in prettier.config.js (print width 160, single quotes, tabs).
  • .editorconfig sets indent_style = tab and max_line_length = 160 for code files. Markdown and YAML files use spaces.
  • ESLint and Stylelint are run using pnpm lint. Lint rules should not be disabled via inline comments. Instead, describe the problem and work towards a clean solution.
  • Lists and enumerations in code should be kept in alphanumeric order. This also applies to import specifiers and union type literals.
  • Do not disable ESLint, Stylelint or TypeScript rules inline. Fix the code instead of turning such rules off.
  • ESLint and Stylelint are run using pnpm lint.
  • Lists and enumerations in code should be kept in alphabetical order (see docs/tutorials/NEW_COMPONENT.md).
  • Commit messages follow the Conventional Commits specification.
  • See also the Contributing Guide for more details on coding conventions and best practices.
  • Spell "KoliBri" with this casing in all documentation and code. The only exception is the component named KolKolibri.
  • Use ESM import syntax in browser code and scripts whenever supported, instead of require imports.
  • Do not create barrel files (e.g. index.ts that re-export modules). Import modules directly instead.
  • Do not place constant declarations before import statements; imports must always be at the very top of the file.
  • Scripts must be platform-independent: All scripts in the scripts/ folder must work on Windows, macOS, and Linux without requiring external tools or platform-specific dependencies. Use Node.js built-in modules instead of external command-line tools like rg, grep, find, etc.

Linting and Formatting

  • Run pnpm lint to check for linting errors across all packages. This script runs ESLint, Stylelint and TypeScript checks.
    • ⚠️ Note: TypeScript type checking in the lint script can require built artifacts. If you've made source code changes, lint will handle any necessary compilation. Explicit pre-build is typically unnecessary.
    • You can try to automatically fix linting issues with pnpm lint:eslint --fix, but this may not resolve all issues.
  • Run pnpm format to format all code files using Prettier. You can try to automatically fix linting issues with pnpm format -w, but this may not resolve all issues.
  • If your pull request only modifies Markdown files, skip pnpm build, pnpm lint and pnpm test. Just format the Markdown using pnpm format or Prettier.

Pre-commit checklist

  • Always run pnpm format (or pnpm --filter <package> format for a single workspace) right before committing. Formatting failures are one of the most common reasons for blocked quality gates, so make this the last step before git commit even for documentation-only changes.
  • For SCSS changes: Always run pnpm lint:stylelint --fix (or pnpm --filter <package> lint:stylelint --fix) to automatically correct formatting, property order, and selector structure rules before committing. This ensures compliance with BEM structure and style consistency.
  • After formatting, re-stage affected files with git add -u so the formatted content is what gets committed.

Testing

  • Run pnpm test from the repository root to execute all unit and integration tests.
    • ⚠️ Note: Test runners (Vitest, Jest, Playwright, etc.) execute an implicit build automatically before running tests. Do NOT run a separate pnpm build beforehand — this wastes time. The test scripts handle compilation and type checking internally.
  • Visual and snapshot tests can be updated with pnpm test:update or via the update-snapshots.yml GitHub workflow (see CONTRIBUTING.md).
  • Individual packages provide their own test scripts (e.g. pnpm --filter @public-ui/components test:unit).
    • These also perform implicit builds, so explicit pre-build is unnecessary.

Pull Request Guidelines

  • PR titles should be meaningful as they appear in the release notes.
  • Every PR must link to its issue and contain only changes related to that issue.
  • Ensure automated tests pass and manual testing is completed when required.
  • Update documentation or migration guides if your changes affect them.