diff --git a/.agents/skills/bump/SKILL.md b/.agents/skills/bump/SKILL.md index befbe64..dcd170b 100644 --- a/.agents/skills/bump/SKILL.md +++ b/.agents/skills/bump/SKILL.md @@ -10,8 +10,8 @@ description: 'Bump package versions and update CHANGELOG.md so consumers can see ## Workflow 1. **Identify affected packages**: `git diff origin/main...HEAD --stat` and `git log origin/main..HEAD` show which packages changed. Ignore packages with no consumer-facing change. -2. **Bump versions**: increment the patch number (`0.0.x` + 1) in each affected `/package.json`. The repository convention is one bump per feature branch, folded into the feature commit. -3. **Align internal peer dependencies**: if a package now relies on something introduced in another package of the same branch (e.g. `react` consuming new `styles` CSS classes), update the corresponding `peerDependencies` range (e.g. `"@ippon-ui/styles": "~0.0.7"`). +2. **Bump versions**: in each affected `/package.json`, increment the patch number (`0.0.x` + 1), or the minor number (`0.x.0`) if the branch contains a breaking change (see [Breaking changes](#breaking-changes)). The repository convention is one bump per feature branch, folded into the feature commit. +3. **Align internal peer dependencies**: if a package now relies on something introduced in another package of the same branch (e.g. `react` consuming new `styles` CSS classes), update the corresponding `peerDependencies` range (e.g. `"@ippon-ui/styles": "~0.0.7"`). Moving that range to a breaking version of the other package is itself breaking for the consumers of this package, so bump this package's minor number too. 4. **Reinstall**: run `mise setup` so the lockfile stays consistent. 5. **Update `CHANGELOG.md`**: add a release entry right after the introduction, above the previous entries. 6. **Verify**: `mise build`, `mise lint-ci` and `mise test-unit-ci` must pass. @@ -34,6 +34,21 @@ description: 'Bump package versions and update CHANGELOG.md so consumers can see - Use the [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) categories: `Added`, `Changed`, `Deprecated`, `Removed`, `Fixed`, `Security`. Only include the categories that apply. - One line per change, in English, consumer-focused: describe what the consumer gets or must adapt, not internal details (CI, tests, tooling stay out unless they affect consumers). - Wrap component, class, prop and token names in backticks. +- Start a breaking line with `- **Breaking** — ` and give the migration: what to replace with what, and what visibly breaks if the consumer does nothing. + +## Breaking changes + +The packages are pre-1.0, where a patch bump is not enough to protect consumers: `~0.0.11` means `>=0.0.11 <0.1.0`, so a consumer on `~0.0.10` installs `0.0.12` automatically and inherits the break with no signal. Only a minor bump falls outside their range. **A branch containing a breaking change bumps the minor number, never the patch number.** + +A change is breaking as soon as an existing consumer must edit something to keep the current behaviour. In this repository that mostly means: + +- `styles`: a CSS class renamed, removed, or now required somewhere it was not (hand-written HTML has no compiler to catch it), a Pug mixin option renamed or removed, a design token removed or renamed. +- `react`: a component or a prop renamed or removed, a prop becoming required, a rendered structure that consumers select from their own CSS or tests. +- `icons`: an icon name removed or renamed. + +Recomposing a component out of a new atom is a typical breaking case: the React component keeps working, but the markup it documents changed, so hand-written HTML must follow. Look for it in `git diff origin/main...HEAD` on the `.pug` and `.scss` files, not only in the public API. + +When in doubt, ask what a consumer already on the previous version has to do after upgrading. If the answer is not "nothing", it is breaking. ## Conventions diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ea7837..5e33f44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ All notable changes to the Ippon UI packages are documented in this file, so con The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), with one entry per release listing the affected package versions. +## 2026-07-29 — @ippon-ui/styles 0.1.0 · @ippon-ui/react 0.1.0 + +### Added + +- `error-area` molecule: fills a zone whose content could not be loaded, with a title, a description and no frame of its own so it sits inside the card or grid cell that already draws one. An action (a retry button) and a detail are both optional; the detail hides a stack trace or an error payload behind a native `details` disclosure. Its padding grows once the surrounding `container` is wide enough. +- `IpponErrorArea` React component: `title` and `description` props, children rendered as the action slot, and an optional `detailMessage` accepting a `string` or an `Error` (rendering its `stack`, falling back to its `message`) together with the required `detailLabel` and an optional `language`. +- `icon-surface` atom: an icon laid on a rounded surface, with `-success`, `-error`, `-information` and `-warning` alternatives on a neutral default. +- `IpponIconSurface` React component with a `color` prop. +- `code` atom: a preformatted code block carrying the frame and the monospace typography, scrolling instead of stretching its container. Giving a language adds the `language-*` class [Prism](https://prismjs.com) styles and reads. Its documentation lists the stylesheet and scripts a page must load for Prism to colorize anything, and what the theme takes over on the typography. +- `IpponCode` React component with `language` and `className` props. +- `code` typography tokens (`--ippon-typography-code-font`, `-size`, `-line-height`, `-weight`). + +### Changed + +- `prism-ippon.css` uses the new `--ippon-typography-code-font` token instead of a hard-coded font stack. That token is currently `monospace` alone, so highlighted code now renders with the browser default monospace font rather than the previous `Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono'` stack. +- **Breaking** — `import-file` molecule renders its icon through the new `icon-surface` atom, which changes the markup it expects. Hand-written HTML must replace `ippon-import-file--icon` with `ippon-icon-surface` on the icon wrapper, and add `ippon-import-file---icon-surface` next to `ippon-import-file` on the root label. Without the first change the round surface behind the icon disappears; without the second it stops turning blue on hover and drag over. `IpponImportFile` consumers have nothing to do, unless they select `.ippon-import-file--icon` from their own CSS or tests. + ## 2026-07-24 — @ippon-ui/styles 0.0.11 ### Added diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 708b975..6075588 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,6 +78,9 @@ importers: '@types/node': specifier: ^24.12.3 version: 24.12.4 + '@types/prismjs': + specifier: ^1.26.6 + version: 1.26.6 '@types/react': specifier: ^19.2.14 version: 19.2.14 @@ -111,6 +114,9 @@ importers: jsdom: specifier: ^29.1.1 version: 29.1.1 + prismjs: + specifier: 1.30.0 + version: 1.30.0 react: specifier: ^19.0.0 version: 19.2.6 @@ -153,6 +159,9 @@ importers: postcss-scss: specifier: 4.0.9 version: 4.0.9(postcss@8.5.15) + prismjs: + specifier: 1.30.0 + version: 1.30.0 sass: specifier: 1.99.0 version: 1.99.0 @@ -1730,6 +1739,9 @@ packages: '@types/node@24.12.4': resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==} + '@types/prismjs@1.26.6': + resolution: {integrity: sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==} + '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: @@ -6253,6 +6265,8 @@ snapshots: dependencies: undici-types: 7.16.0 + '@types/prismjs@1.26.6': {} + '@types/react-dom@19.2.3(@types/react@19.2.14)': dependencies: '@types/react': 19.2.14 diff --git a/react/package.json b/react/package.json index 4346701..eb19565 100644 --- a/react/package.json +++ b/react/package.json @@ -1,7 +1,7 @@ { "name": "@ippon-ui/react", "description": "Ippon UI React Component Library", - "version": "0.0.9", + "version": "0.1.0", "license": "Apache-2.0", "repository": { "type": "git", @@ -39,15 +39,12 @@ }, "peerDependencies": { "@ippon-ui/icons": "~0.0.2", - "@ippon-ui/styles": "~0.0.10", + "@ippon-ui/styles": "~0.1.0", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0", "@babel/core": "^7.29.0", - "vite-plugin-dts": "^5.0.1", "@eslint/js": "^10.0.1", "@ippon-ui/icons": "workspace:*", "@ippon-ui/styles": "workspace:*", @@ -58,6 +55,7 @@ "@testing-library/react": "^16.3.2", "@types/babel__core": "^7.20.5", "@types/node": "^24.12.3", + "@types/prismjs": "^1.26.6", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.1", @@ -69,10 +67,14 @@ "eslint-plugin-storybook": "^10.4.4", "globals": "^17.6.0", "jsdom": "^29.1.1", + "prismjs": "1.30.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", "storybook": "^10.4.4", "typescript": "~6.0.2", "typescript-eslint": "^8.59.2", "vite": "^8.0.12", + "vite-plugin-dts": "^5.0.1", "vitest": "4.1.6" } } diff --git a/react/src/IconSurface.ts b/react/src/IconSurface.ts new file mode 100644 index 0000000..39f5a5f --- /dev/null +++ b/react/src/IconSurface.ts @@ -0,0 +1 @@ +export type IpponIconSurfaceColor = 'success' | 'error' | 'information' | 'warning'; diff --git a/react/src/IpponCode.tsx b/react/src/IpponCode.tsx new file mode 100644 index 0000000..51bdfd7 --- /dev/null +++ b/react/src/IpponCode.tsx @@ -0,0 +1,20 @@ +import { clsx } from 'clsx'; +import type { DataSelectableWithChildren } from './DataSelectable.ts'; + +type IpponCodeProps = DataSelectableWithChildren<{ + language?: string; + className?: string; +}>; + +export const IpponCode = (props: IpponCodeProps) => { + const languageClass = props.language ? `language-${props.language}` : undefined; + + return ( +
+      {props.children}
+    
+ ); +}; diff --git a/react/src/IpponErrorArea.tsx b/react/src/IpponErrorArea.tsx new file mode 100644 index 0000000..657eba6 --- /dev/null +++ b/react/src/IpponErrorArea.tsx @@ -0,0 +1,71 @@ +import type { DataSelectableWithChildren } from './DataSelectable.ts'; +import { IpponCode } from './IpponCode.tsx'; +import { IpponIcon } from './IpponIcon.tsx'; +import { IpponIconSurface } from './IpponIconSurface.tsx'; +import { IpponIon } from './IpponIon.tsx'; +import { IpponText } from './IpponText.tsx'; +import { IpponVSpace } from './IpponVSpace.tsx'; + +type IpponErrorAreaCommonProps = { + title: string; + description: string; +}; + +type IpponErrorAreaWithoutDetailProps = IpponErrorAreaCommonProps & { + detailMessage?: never; + detailLabel?: never; + language?: never; +}; + +type IpponErrorAreaWithDetailProps = IpponErrorAreaCommonProps & { + detailMessage: string | Error; + detailLabel: string; + language?: string; +}; + +type IpponErrorAreaVanillaProps = IpponErrorAreaWithoutDetailProps | IpponErrorAreaWithDetailProps; + +type IpponErrorAreaProps = DataSelectableWithChildren; + +const toDetailContent = (detailMessage: string | Error): string => { + if (detailMessage instanceof Error) { + return detailMessage.stack ?? detailMessage.message; + } + return detailMessage; +}; + +export const IpponErrorArea = (props: IpponErrorAreaProps) => ( +
+ + + + + + + {props.title} + + + {props.description} + + + {props.detailMessage && ( +
+ + + + {props.detailLabel} + + + + {toDetailContent(props.detailMessage)} + +
+ )} + {props.children} +
+
+); diff --git a/react/src/IpponIconSurface.tsx b/react/src/IpponIconSurface.tsx new file mode 100644 index 0000000..aa1d0fb --- /dev/null +++ b/react/src/IpponIconSurface.tsx @@ -0,0 +1,17 @@ +import { clsx } from 'clsx'; +import type { DataSelectableWithChildren } from './DataSelectable.ts'; +import { optionalToAlternativeClass } from './CAP.ts'; +import type { IpponIconSurfaceColor } from './IconSurface.ts'; + +type IpponIconSurfaceProps = DataSelectableWithChildren<{ + color?: IpponIconSurfaceColor; +}>; + +export const IpponIconSurface = (props: IpponIconSurfaceProps) => ( +
+ {props.children} +
+); diff --git a/react/src/IpponImportFile.tsx b/react/src/IpponImportFile.tsx index 8c7be4d..eeb9bc9 100644 --- a/react/src/IpponImportFile.tsx +++ b/react/src/IpponImportFile.tsx @@ -4,6 +4,7 @@ import { useRef, useState } from 'react'; import { Optional } from './Optional.ts'; import type { DataSelectable } from './DataSelectable.ts'; import { IpponIcon } from './IpponIcon.tsx'; +import { IpponIconSurface } from './IpponIconSurface.tsx'; import { IpponText } from './IpponText.tsx'; import { IpponVSpace } from './IpponVSpace.tsx'; import { toAlternativeClass } from './CAP.ts'; @@ -92,7 +93,7 @@ export const IpponImportFile = (props: IpponImportFileProps) => { return (