diff --git a/.dependency-cruiser.cjs b/.dependency-cruiser.cjs new file mode 100644 index 000000000..5ae114e5c --- /dev/null +++ b/.dependency-cruiser.cjs @@ -0,0 +1,407 @@ +/** @type {import('dependency-cruiser').IConfiguration} */ +module.exports = { + forbidden: [ + { + name: 'no-circular', + severity: 'warn', + comment: + 'This dependency is part of a circular relationship. You might want to revise ' + + 'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ', + from: {}, + to: { + circular: true + } + }, + { + name: 'no-orphans', + comment: + "This is an orphan module - it's likely not used (anymore?). Either use it or " + + "remove it. If it's logical this module is an orphan (i.e. it's a config file), " + + "add an exception for it in your dependency-cruiser configuration. By default " + + "this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " + + "files (.d.ts), tsconfig.json and some of the babel and webpack configs.", + severity: 'warn', + from: { + orphan: true, + pathNot: [ + '(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$', // dot files + '[.]d[.]ts$', // TypeScript declaration files + '(^|/)tsconfig[.]json$', // TypeScript config + '(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$' // other configs + ] + }, + to: {}, + }, + { + name: 'no-deprecated-core', + comment: + 'A module depends on a node core module that has been deprecated. Find an alternative - these are ' + + "bound to exist - node doesn't deprecate lightly.", + severity: 'warn', + from: {}, + to: { + dependencyTypes: [ + 'core' + ], + path: [ + '^v8/tools/codemap$', + '^v8/tools/consarray$', + '^v8/tools/csvparser$', + '^v8/tools/logreader$', + '^v8/tools/profile_view$', + '^v8/tools/profile$', + '^v8/tools/SourceMap$', + '^v8/tools/splaytree$', + '^v8/tools/tickprocessor-driver$', + '^v8/tools/tickprocessor$', + '^node-inspect/lib/_inspect$', + '^node-inspect/lib/internal/inspect_client$', + '^node-inspect/lib/internal/inspect_repl$', + '^async_hooks$', + '^punycode$', + '^domain$', + '^constants$', + '^sys$', + '^_linklist$', + '^_stream_wrap$' + ], + } + }, + { + name: 'not-to-deprecated', + comment: + 'This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later ' + + 'version of that module, or find an alternative. Deprecated modules are a security risk.', + severity: 'warn', + from: {}, + to: { + dependencyTypes: [ + 'deprecated' + ] + } + }, + // { + // name: 'no-non-package-json', + // severity: 'error', + // comment: + // "This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " + + // "That's problematic as the package either (1) won't be available on live (2 - worse) will be " + + // "available on live with an non-guaranteed version. Fix it by adding the package to the dependencies " + + // "in your package.json.", + // from: {}, + // to: { + // dependencyTypes: [ + // 'npm-no-pkg', + // 'npm-unknown' + // ] + // } + // }, + // { + // name: 'not-to-unresolvable', + // comment: + // "This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " + + // 'module: add it to your package.json. In all other cases you likely already know what to do.', + // severity: 'error', + // from: {}, + // to: { + // couldNotResolve: true + // } + // }, + { + name: 'no-duplicate-dep-types', + comment: + "Likely this module depends on an external ('npm') package that occurs more than once " + + "in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " + + "maintenance problems later on.", + severity: 'warn', + from: {}, + to: { + moreThanOneDependencyType: true, + // as it's pretty common to have a type import be a type only import + // _and_ (e.g.) a devDependency - don't consider type-only dependency + // types for this rule + dependencyTypesNot: ["type-only"] + } + }, + + /* rules you might want to tweak for your specific situation: */ + + { + name: 'not-to-spec', + comment: + 'This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. ' + + "If there's something in a spec that's of use to other modules, it doesn't have that single " + + 'responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.', + severity: 'error', + from: {}, + to: { + path: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$' + } + }, + { + name: 'not-to-dev-dep', + severity: 'error', + comment: + "This module depends on an npm package from the 'devDependencies' section of your " + + 'package.json. It looks like something that ships to production, though. To prevent problems ' + + "with npm packages that aren't there on production declare it (only!) in the 'dependencies'" + + 'section of your package.json. If this module is development only - add it to the ' + + 'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration', + from: { + path: '^(packages|apps)', + pathNot: [ + '^packages/eslint-config', + '^packages/prettier-config', + 'apps/nextjs/tailwind.config.cjs', + 'jest[.]config[.](js|mjs|cjs|ts)$', + '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$', + '(^|/)__tests__/', + '(^|/)test/', + '(^|/)tests/', + '(^|/)tests-e2e/' + ] + }, + to: { + dependencyTypes: [ + 'npm-dev', + ], + // type only dependencies are not a problem as they don't end up in the + // production code or are ignored by the runtime. + dependencyTypesNot: [ + 'type-only' + ], + pathNot: [ + 'node_modules/@types/' + ] + } + }, + { + name: 'optional-deps-used', + severity: 'info', + comment: + "This module depends on an npm package that is declared as an optional dependency " + + "in your package.json. As this makes sense in limited situations only, it's flagged here. " + + "If you're using an optional dependency here by design - add an exception to your" + + "dependency-cruiser configuration.", + from: {}, + to: { + dependencyTypes: [ + 'npm-optional' + ] + } + }, + { + name: 'peer-deps-used', + comment: + "This module depends on an npm package that is declared as a peer dependency " + + "in your package.json. This makes sense if your package is e.g. a plugin, but in " + + "other cases - maybe not so much. If the use of a peer dependency is intentional " + + "add an exception to your dependency-cruiser configuration.", + severity: 'warn', + from: {}, + to: { + dependencyTypes: [ + 'npm-peer' + ] + } + } + ], + options: { + + /* Which modules not to follow further when encountered */ + doNotFollow: { + /* path: an array of regular expressions in strings to match against */ + path: ['(^|/)node_modules/','node_modules', '(^|/)dist/', '(^|/)build/', '(^|/)out/'] + }, + + /* Which modules to exclude */ + exclude : { + /* path: an array of regular expressions in strings to match against */ + path: [ + '^apps/nextjs/playwright-report', + '(^|/)\\.turbo/', + '(^|/)\\.github/', + '(^|/)coverage/', + '(^|/)\\.next/', + '(^|/)\\.storybook/', + '\\.test\\.(js|ts|jsx|tsx)$', + '\\.spec\\.(js|ts|jsx|tsx)$', + '\\.stories\\.(js|ts|jsx|tsx)$' + ] + }, + + /* Which modules to exclusively include (array of regular expressions in strings) + dependency-cruiser will skip everything not matching this pattern + */ + // includeOnly : [''], + + /* List of module systems to cruise. + When left out dependency-cruiser will fall back to the list of _all_ + module systems it knows of. It's the default because it's the safe option + It might come at a performance penalty, though. + moduleSystems: ['amd', 'cjs', 'es6', 'tsd'] + + As in practice only commonjs ('cjs') and ecmascript modules ('es6') + are widely used, you can limit the moduleSystems to those. + */ + + // moduleSystems: ['cjs', 'es6'], + + /* + false: don't look at JSDoc imports (the default) + true: dependency-cruiser will detect dependencies in JSDoc-style + import statements. Implies "parser": "tsc", so the dependency-cruiser + will use the typescript parser for JavaScript files. + + For this to work the typescript compiler will need to be installed in the + same spot as you're running dependency-cruiser from. + */ + // detectJSDocImports: true, + + /* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/main/' + to open it on your online repo or `vscode://file/${process.cwd()}/` to + open it in visual studio code), + */ + // prefix: `vscode://file/${process.cwd()}/`, + + /* false (the default): ignore dependencies that only exist before typescript-to-javascript compilation + true: also detect dependencies that only exist before typescript-to-javascript compilation + "specify": for each dependency identify whether it only exists before compilation or also after + */ + tsPreCompilationDeps: true, + + /* list of extensions to scan that aren't javascript or compile-to-javascript. + Empty by default. Only put extensions in here that you want to take into + account that are _not_ parsable. + */ + // extraExtensionsToScan: [".json", ".jpg", ".png", ".svg", ".webp"], + + /* if true combines the package.jsons found from the module up to the base + folder the cruise is initiated from. Useful for how (some) mono-repos + manage dependencies & dependency definitions. + */ + // combinedDependencies: false, + + /* if true leave symlinks untouched, otherwise use the realpath */ + // preserveSymlinks: false, + + /* TypeScript project file ('tsconfig.json') to use for + (1) compilation and + (2) resolution (e.g. with the paths property) + + The (optional) fileName attribute specifies which file to take (relative to + dependency-cruiser's current working directory). When not provided + defaults to './tsconfig.json'. + */ + tsConfig: { + fileName: 'tsconfig.json' + }, + + /* Webpack configuration to use to get resolve options from. + + The (optional) fileName attribute specifies which file to take (relative + to dependency-cruiser's current working directory. When not provided defaults + to './webpack.conf.js'. + + The (optional) `env` and `arguments` attributes contain the parameters + to be passed if your webpack config is a function and takes them (see + webpack documentation for details) + */ + // webpackConfig: { + // fileName: 'webpack.config.js', + // env: {}, + // arguments: {} + // }, + + /* Babel config ('.babelrc', '.babelrc.json', '.babelrc.json5', ...) to use + for compilation + */ + // babelConfig: { + // fileName: '.babelrc', + // }, + + /* List of strings you have in use in addition to cjs/ es6 requires + & imports to declare module dependencies. Use this e.g. if you've + re-declared require, use a require-wrapper or use window.require as + a hack. + */ + // exoticRequireStrings: [], + + /* options to pass on to enhanced-resolve, the package dependency-cruiser + uses to resolve module references to disk. The values below should be + suitable for most situations + + If you use webpack: you can also set these in webpack.conf.js. The set + there will override the ones specified here. + */ + enhancedResolveOptions: { + /* What to consider as an 'exports' field in package.jsons */ + exportsFields: ["exports"], + /* List of conditions to check for in the exports field. + Only works when the 'exportsFields' array is non-empty. + */ + conditionNames: ["import", "require", "node", "default", "types"], + /* + The extensions, by default are the same as the ones dependency-cruiser + can access (run `npx depcruise --info` to see which ones that are in + _your_ environment). If that list is larger than you need you can pass + the extensions you actually use (e.g. [".js", ".jsx"]). This can speed + up module resolution, which is the most expensive step. + */ + // extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"], + /* What to consider a 'main' field in package.json */ + mainFields: ["module", "main", "types", "typings"], + /* + A list of alias fields in package.jsons + See [this specification](https://github.com/defunctzombie/package-browser-field-spec) and + the webpack [resolve.alias](https://webpack.js.org/configuration/resolve/#resolvealiasfields) + documentation + + Defaults to an empty array (= don't use alias fields). + */ + // aliasFields: ["browser"], + }, + reporterOptions: { + dot: { + /* pattern of modules that can be consolidated in the detailed + graphical dependency graph. The default pattern in this configuration + collapses everything in node_modules to one folder deep so you see + the external modules, but their innards. + */ + collapsePattern: 'node_modules/(?:@[^/]+/[^/]+|[^/]+)', + + /* Options to tweak the appearance of your graph.See + https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions + for details and some examples. If you don't specify a theme + dependency-cruiser falls back to a built-in one. + */ + // theme: { + // graph: { + // /* splines: "ortho" gives straight lines, but is slow on big graphs + // splines: "true" gives bezier curves (fast, not as nice as ortho) + // */ + // splines: "true" + // }, + // } + }, + archi: { + /* pattern of modules that can be consolidated in the high level + graphical dependency graph. If you use the high level graphical + dependency graph reporter (`archi`) you probably want to tweak + this collapsePattern to your situation. + */ + collapsePattern: '^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)', + + /* Options to tweak the appearance of your graph. If you don't specify a + theme for 'archi' dependency-cruiser will use the one specified in the + dot section above and otherwise use the default one. + */ + // theme: { }, + }, + "text": { + "highlightFocused": true + }, + } + } +}; +// generated: dependency-cruiser@16.7.0 on 2024-12-10T09:31:39.848Z diff --git a/.gitignore b/.gitignore index 60c9debbe..f71235e32 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,6 @@ yarn-error.log* .vscode/*.settings.json .sonar -.sonar/**/* \ No newline at end of file +.sonar/**/* + +dependency-graph.svg \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index e9af3c6cd..80e519a17 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -5,6 +5,7 @@ "bradlc.vscode-tailwindcss", "Prisma.prisma", "orta.vscode-jest", - "ms-playwright.playwright" + "ms-playwright.playwright", + "sonarsource.sonarlint-vscode" ] } diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md index 0a5067ea1..2eb626a29 100644 --- a/CHANGE_LOG.md +++ b/CHANGE_LOG.md @@ -1,3 +1,19 @@ +# [1.18.0](https://github.com/oaknational/oak-ai-lesson-assistant/compare/v1.17.1...v1.18.0) (2024-12-11) + + +### Bug Fixes + +* update eslint config and lint errors ([#422](https://github.com/oaknational/oak-ai-lesson-assistant/issues/422)) ([34774b5](https://github.com/oaknational/oak-ai-lesson-assistant/commit/34774b5b404c154d8c8aa10ae5b687507dc09c85)) +* moderation persist scores [AI-696] ([#418](https://github.com/oaknational/oak-ai-lesson-assistant/issues/418)) ([7539661](https://github.com/oaknational/oak-ai-lesson-assistant/commit/7539661e22fa0e334138e0f796a700662e4e37e3)) +* more flexible and efficient solution for images in docs ([#411](https://github.com/oaknational/oak-ai-lesson-assistant/issues/411)) ([abbff85](https://github.com/oaknational/oak-ai-lesson-assistant/commit/abbff8545476f6c045331a63290fc8f57cf3f54e)) +* upgrade typescript + prettier + eslint with a single shared linting config ([#424](https://github.com/oaknational/oak-ai-lesson-assistant/issues/424)) ([1f4aa9d](https://github.com/oaknational/oak-ai-lesson-assistant/commit/1f4aa9dde84402df089ce4d6947472d389f576a0)) + + +### Features + +* report google drive storage quota ([#425](https://github.com/oaknational/oak-ai-lesson-assistant/issues/425)) ([a0cb485](https://github.com/oaknational/oak-ai-lesson-assistant/commit/a0cb4859ddb975d831a5b64d8d789ee8597aeda8)) +* test coverage ([#406](https://github.com/oaknational/oak-ai-lesson-assistant/issues/406)) ([00767a0](https://github.com/oaknational/oak-ai-lesson-assistant/commit/00767a0148456f57f8fa21e0cb0149cf849f3574)) + ## [1.17.1](https://github.com/oaknational/oak-ai-lesson-assistant/compare/v1.17.0...v1.17.1) (2024-12-03) diff --git a/README.md b/README.md index fb3ee86ba..52b6c5a00 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,16 @@ Oak AI Lesson Assistant is a project focused on experimenting with AI models and - [End-to-end tests](#end-to-end-tests) - [Playwright tags](#playwright-tags) - [Testing in VSCode](#testing-in-vscode) + - [Standards](#standards) + - [Typescript](#typescript) + - [ES Modules](#esmodules) + - [CommonJS](#commonjs) + - [Code quality](#quality) + - [Sonar](#sonar) + - [ESLint](#eslint) + - [Prettier](#prettier) + - [Tsconfig]("#tsconfig) + - [Dependency cruiser](#dependency-cruiser) - [Release process](#release-process) - [PNPM / dependency problems](#pnpm--dependency-problems) - [External contributions](#external-contributions) @@ -176,6 +186,68 @@ Our Playwright tests are organised with tags: Install the Jest and Playwright extensions recommended in the workspace config. Testing icons should appear in the gutter to the left of each test when viewing a test file. Clicking the icon will run the test. The testing tab in the VSCode nav bar provides an overview. +## Standards + +### Typescript + +By default, we develop in Typescript and aim to be up to date with the latest version. New code should default to being written in Typescript unless it is not possible. + +### ES Modules + +All packages are configured to be ES Modules. + +### CommonJS + +Currently NextJS, Tailwind and some other tools has some code which needs to be CommonJS. For these files, you should use the .cjs extension so that the correct linting rules are applied. + +## Code quality + +We use several tools to ensure code quality in the codebase and for checks during development. These checks run on each PR in Github to ensure we maintain good code quality. You can also run many of these checks locally before making a PR. + +### Sonar + +If you are using VS Code or Cursor, consider installing the SonarQube for IDE extension. This will give you feedback while you were as to any code quality or security issues that it has detected. + +If you would like to run a Sonar scan locally, use the following command: + +```shell +pnpm sonar +``` + +You will need to log in to Sonar when prompted the first time. This will generate a full report for you of your local development environment. Usually it is easier to make a PR and have this run for you automatically. + +### ESLint + +We have a single ESLint config for the whole monorepo. You will find it in packages/eslint-config. + +This is using the latest version of ESLint and you should note that the config file format has changed to the "Flat file" config in version 9. + +Each package does not have its own ESLint config by default. Instead we have a single config file, with regex path matchers to turn on/off rules that are specific for each package. This can be overridden and you can see an example of that in the logger package. + +Each package specifies in its package.json file that it should use this shared config and there is a root ESLint config file for the whole mono repo which specifies that it should do the same. + +To check for linting errors, run the following command: + +```shell +pnpm lint +``` + +If you want to check for linting errors in an individual package, cd into that package and run the same command. + +### Prettier + +We also have a single Prettier config, which is located in packages/prettier-config. In general there should be no need to change this on a per-package basis. + +### Tsconfig + +We have an overall tsconfig.json file which specifies the overall Typescript configuration for the project. Then each package extends from it. + +You can check the codebase for any Typescript issues by running the following command: + +```shell +pnpm type-check +``` + ## Release process The current release process is fully documented [in Notion](https://www.notion.so/oaknationalacademy/Branch-Strategy-and-Release-Process-ceeb32937af0426ba495565288e18844?pvs=4), but broadly works as follows: diff --git a/apps/nextjs/.storybook/chromatic.ts b/apps/nextjs/.storybook/chromatic.ts new file mode 100644 index 000000000..bc755b188 --- /dev/null +++ b/apps/nextjs/.storybook/chromatic.ts @@ -0,0 +1,91 @@ +import "@storybook/csf"; + +type ChromaticModes = "mobile" | "mobile-wide" | "desktop" | "desktop-wide"; + +export function chromaticParams(modes: ChromaticModes[]) { + return { + chromatic: { + modes: { + ...(modes.includes("mobile") && { + mobile: { viewport: "mobile" }, + }), + ...(modes.includes("mobile-wide") && { + mobile: { viewport: "mobile-wide" }, + }), + ...(modes.includes("desktop") && { + desktop: { viewport: "desktop" }, + }), + ...(modes.includes("desktop-wide") && { + "desktop-wide": { viewport: "desktopWide" }, + }), + }, + }, + }; +} + +declare module "@storybook/csf" { + interface Parameters { + /** + * Parameters for chromatic + */ + chromatic?: { + /** + * Delay capture for a fixed time (in milliseconds) to allow your story to get into + * the intended state + * + * @see [delaying snapshots chromatic documentation](https://www.chromatic.com/docs/delay) + */ + delay?: number; + /** + * Override this behavior in instances where a single pixel change is not flagged by + * Chromatic but should be + * + * * @see [anti-aliasing chromatic documentation](https://www.chromatic.com/docs/threshold#anti-aliasing) + * + * @default false + */ + diffIncludeAntiAliasing?: boolean; + /** + * The diffThreshold parameter allows you to fine tune the threshold for visual change + * between snapshots before they're flagged by Chromatic. Sometimes you need assurance + * to the sub-pixel and other times you want to skip visual noise generated by + * non-deterministic rendering such as anti-aliasing. + * + * 0 is the most accurate. 1 is the least accurate. + * + * @default 0.063 + */ + diffThreshold?: number; + /** + * You can omit stories entirely from Chromatic testing using the disable story parameter. + * + * @see [ignoring elements chromatic documentation](https://www.chromatic.com/docs/ignoring-elements) + */ + disable?: boolean; + /** + * Modes + * + * @see [modes chromatic documentation](https://www.chromatic.com/docs/modes) + */ + modes?: Record< + string, + { + viewport?: string | number; + theme?: "light" | "dark"; + backgrounds?: { value: string }; + } + >; + /** + * Define one or more viewport sizes to capture. Note, units are considered in pixels + */ + viewports?: number[]; + /** + * To specify that Chromatic should pause the animation at the end instead of reseting + * them to their beginning state. + * + * @see [animations chromatic documentation](https://www.chromatic.com/docs/animations) + */ + pauseAnimationAtEnd?: boolean; + }; + } +} diff --git a/apps/nextjs/.storybook/preview.tsx b/apps/nextjs/.storybook/preview.tsx index 99b2ad60e..55f606d81 100644 --- a/apps/nextjs/.storybook/preview.tsx +++ b/apps/nextjs/.storybook/preview.tsx @@ -15,6 +15,7 @@ import { DialogProvider } from "../src/components/AppComponents/DialogContext"; import { AnalyticsProvider } from "../src/mocks/analytics/provider"; import { ClerkDecorator } from "../src/mocks/clerk/ClerkDecorator"; import { TRPCReactProvider } from "../src/utils/trpc"; +import { chromaticParams } from "./chromatic"; import { RadixThemeDecorator } from "./decorators/RadixThemeDecorator"; import "./preview.css"; @@ -28,6 +29,27 @@ const preview: Preview = { date: /Date$/i, }, }, + viewport: { + viewports: { + mobile: { + name: "Mobile", + styles: { width: "375px", height: "800px" }, + }, + mobileWide: { + name: "Mobile Wide", + styles: { width: "430px", height: "930px" }, + }, + desktop: { + name: "Desktop", + styles: { width: "1200px", height: "1000px" }, + }, + desktopWide: { + name: "Desktop Wide", + styles: { width: "1400px", height: "1000px" }, + }, + }, + }, + ...chromaticParams(["desktop"]), }, loaders: [mswLoader], }; diff --git a/apps/nextjs/src/ai-apps/lesson-planner/state/actions.ts b/apps/nextjs/src/ai-apps/lesson-planner/state/actions.ts index 3b68c7c5f..16ef00a41 100644 --- a/apps/nextjs/src/ai-apps/lesson-planner/state/actions.ts +++ b/apps/nextjs/src/ai-apps/lesson-planner/state/actions.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-duplicate-enum-values */ import type { RateLimitInfo } from "@oakai/api/src/types"; import type { KeyStageName, SubjectName } from "@oakai/core"; import type { diff --git a/apps/nextjs/src/app/aila/[id]/download/DownloadView.stories.tsx b/apps/nextjs/src/app/aila/[id]/download/DownloadView.stories.tsx index 6021b1dfe..4cacabe31 100644 --- a/apps/nextjs/src/app/aila/[id]/download/DownloadView.stories.tsx +++ b/apps/nextjs/src/app/aila/[id]/download/DownloadView.stories.tsx @@ -1,6 +1,7 @@ import type { AilaPersistedChat } from "@oakai/aila/src/protocol/schema"; import type { Meta, StoryObj } from "@storybook/react"; +import { chromaticParams } from "../../../../../.storybook/chromatic"; import { DemoProvider } from "../../../../../src/components/ContextProviders/Demo"; import { DownloadContent } from "./DownloadView"; @@ -9,6 +10,7 @@ const meta: Meta = { component: DownloadContent, parameters: { layout: "fullscreen", + ...chromaticParams(["mobile", "desktop"]), }, decorators: [ (Story) => ( diff --git a/apps/nextjs/src/app/aila/[id]/share/index.stories.tsx b/apps/nextjs/src/app/aila/[id]/share/index.stories.tsx index b8a803caf..b15660f44 100644 --- a/apps/nextjs/src/app/aila/[id]/share/index.stories.tsx +++ b/apps/nextjs/src/app/aila/[id]/share/index.stories.tsx @@ -1,6 +1,7 @@ import type { LooseLessonPlan } from "@oakai/aila/src/protocol/schema"; import type { Meta, StoryObj } from "@storybook/react"; +import { chromaticParams } from "../../../../../.storybook/chromatic"; import ShareChat from "./"; const meta: Meta = { @@ -8,6 +9,7 @@ const meta: Meta = { component: ShareChat, parameters: { layout: "fullscreen", + ...chromaticParams(["mobile", "desktop"]), }, }; diff --git a/apps/nextjs/src/app/aila/help/index.stories.tsx b/apps/nextjs/src/app/aila/help/index.stories.tsx index df567c777..6f1a40e2f 100644 --- a/apps/nextjs/src/app/aila/help/index.stories.tsx +++ b/apps/nextjs/src/app/aila/help/index.stories.tsx @@ -3,6 +3,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { DemoProvider } from "@/components/ContextProviders/Demo"; import { HelpContent } from "."; +import { chromaticParams } from "../../../../.storybook/chromatic"; const meta: Meta = { title: "Pages/Chat/Help", @@ -10,6 +11,7 @@ const meta: Meta = { parameters: { // Including custom decorators changes the layout from fullscreen layout: "fullscreen", + ...chromaticParams(["mobile", "desktop"]), }, decorators: [ (Story) => ( diff --git a/apps/nextjs/src/app/aila/page-contents.tsx b/apps/nextjs/src/app/aila/page-contents.tsx index 49119a78d..8dce45b37 100644 --- a/apps/nextjs/src/app/aila/page-contents.tsx +++ b/apps/nextjs/src/app/aila/page-contents.tsx @@ -11,7 +11,7 @@ const ChatPageContents = ({ id }: { readonly id: string }) => { return ( - + diff --git a/apps/nextjs/src/app/api/chat/errorHandling.test.ts b/apps/nextjs/src/app/api/chat/errorHandling.test.ts index c9f0ebbd3..09e9aa0a4 100644 --- a/apps/nextjs/src/app/api/chat/errorHandling.test.ts +++ b/apps/nextjs/src/app/api/chat/errorHandling.test.ts @@ -27,7 +27,6 @@ describe("handleChatException", () => { const span = { setTag: jest.fn() } as unknown as TracingSpan; const error = new AilaThreatDetectionError("user_abc", "test error"); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const prisma = {} as unknown as PrismaClientWithAccelerate; const response = await handleChatException( @@ -54,7 +53,6 @@ describe("handleChatException", () => { it("should return an error chat message", async () => { const span = { setTag: jest.fn() } as unknown as TracingSpan; const error = new AilaAuthenticationError("test error"); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const prisma = {} as unknown as PrismaClientWithAccelerate; const response = await handleChatException( @@ -84,7 +82,6 @@ describe("handleChatException", () => { 100, Date.now() + 3600 * 1000, ); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const prisma = {} as unknown as PrismaClientWithAccelerate; const response = await handleChatException( @@ -117,7 +114,6 @@ describe("handleChatException", () => { it("should return an error chat message", async () => { const span = { setTag: jest.fn() } as unknown as TracingSpan; const error = new UserBannedError("test error"); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const prisma = {} as unknown as PrismaClientWithAccelerate; const response = await handleChatException( diff --git a/apps/nextjs/src/app/faqs/index.stories.tsx b/apps/nextjs/src/app/faqs/index.stories.tsx index 7eef2c06a..0fa4b4da3 100644 --- a/apps/nextjs/src/app/faqs/index.stories.tsx +++ b/apps/nextjs/src/app/faqs/index.stories.tsx @@ -1,10 +1,14 @@ import type { Meta, StoryObj } from "@storybook/react"; import { FAQPageContent } from "."; +import { chromaticParams } from "../../../.storybook/chromatic"; const meta: Meta = { title: "Pages/FAQs", component: FAQPageContent, + parameters: { + ...chromaticParams(["mobile", "desktop"]), + }, }; export default meta; diff --git a/apps/nextjs/src/app/home-page.stories.tsx b/apps/nextjs/src/app/home-page.stories.tsx index e61e5eab1..15fb1e328 100644 --- a/apps/nextjs/src/app/home-page.stories.tsx +++ b/apps/nextjs/src/app/home-page.stories.tsx @@ -1,10 +1,14 @@ import type { Meta, StoryObj } from "@storybook/react"; +import { chromaticParams } from "../../.storybook/chromatic"; import { HomePageContent } from "./home-page"; const meta: Meta = { title: "Pages/Homepage", component: HomePageContent, + parameters: { + ...chromaticParams(["mobile", "desktop"]), + }, }; export default meta; diff --git a/apps/nextjs/src/app/legal/[slug]/legal.stories.tsx b/apps/nextjs/src/app/legal/[slug]/legal.stories.tsx index 88a049b92..85a0ab9d2 100644 --- a/apps/nextjs/src/app/legal/[slug]/legal.stories.tsx +++ b/apps/nextjs/src/app/legal/[slug]/legal.stories.tsx @@ -1,10 +1,14 @@ import type { Meta, StoryObj } from "@storybook/react"; +import { chromaticParams } from "../../../../.storybook/chromatic"; import { LegalContent } from "./legal"; const meta: Meta = { title: "Pages/Legal/Sanity dynamic", component: LegalContent, + parameters: { + ...chromaticParams(["mobile", "desktop"]), + }, }; export default meta; diff --git a/apps/nextjs/src/app/legal/account-locked/account-locked.stories.tsx b/apps/nextjs/src/app/legal/account-locked/account-locked.stories.tsx index 820cdd4e9..846a1a046 100644 --- a/apps/nextjs/src/app/legal/account-locked/account-locked.stories.tsx +++ b/apps/nextjs/src/app/legal/account-locked/account-locked.stories.tsx @@ -1,10 +1,14 @@ import type { Meta, StoryObj } from "@storybook/react"; +import { chromaticParams } from "../../../../.storybook/chromatic"; import { AccountLocked } from "./account-locked"; const meta: Meta = { title: "Pages/Legal/Account Locked", component: AccountLocked, + parameters: { + ...chromaticParams(["mobile", "desktop"]), + }, }; export default meta; diff --git a/apps/nextjs/src/app/lesson-planner/preview/[slug]/page.tsx b/apps/nextjs/src/app/lesson-planner/preview/[slug]/page.tsx index fcced6393..493eda990 100644 --- a/apps/nextjs/src/app/lesson-planner/preview/[slug]/page.tsx +++ b/apps/nextjs/src/app/lesson-planner/preview/[slug]/page.tsx @@ -16,8 +16,6 @@ async function getData(slug: string) { notFound: true, }; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - // const { updatedAt, createdAt, ...rest } = sharedData; const planSections = sharedData.content; return planSections; diff --git a/apps/nextjs/src/app/prompts/prompts.stories.tsx b/apps/nextjs/src/app/prompts/prompts.stories.tsx index df2e3d10e..e3bbea25c 100644 --- a/apps/nextjs/src/app/prompts/prompts.stories.tsx +++ b/apps/nextjs/src/app/prompts/prompts.stories.tsx @@ -1,10 +1,14 @@ import type { Meta, StoryObj } from "@storybook/react"; +import { chromaticParams } from "../../../.storybook/chromatic"; import { PromptsContent } from "./prompts"; const meta: Meta = { title: "Pages/Prompts", component: PromptsContent, + parameters: { + ...chromaticParams(["mobile", "desktop"]), + }, }; export default meta; diff --git a/apps/nextjs/src/app/quiz-designer/preview/[slug]/page.tsx b/apps/nextjs/src/app/quiz-designer/preview/[slug]/page.tsx index c8d9b3d05..9f9964907 100644 --- a/apps/nextjs/src/app/quiz-designer/preview/[slug]/page.tsx +++ b/apps/nextjs/src/app/quiz-designer/preview/[slug]/page.tsx @@ -14,8 +14,6 @@ async function getData(slug: string) { notFound: true, }; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - // const { updatedAt, createdAt, ...rest } = sharedData; const questions = sharedData.content; diff --git a/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts b/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts index 145ff171e..4f3786afd 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts +++ b/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts @@ -1,9 +1,26 @@ import { useMemo } from "react"; -import type { LooseLessonPlan } from "@oakai/aila/src/protocol/schema"; +import type { + LessonPlanKeys, + LooseLessonPlan, +} from "@oakai/aila/src/protocol/schema"; import { lessonPlanSectionsSchema } from "@oakai/exports/src/schema/input.schema"; import type { ZodIssue } from "zod"; +export type ProgressForDownloads = { + sections: ProgressSection[]; + totalSections: number; + totalSectionsComplete: number; +}; + +export type ProgressSection = { + label: string; + key: LessonPlanKeys; + complete: boolean; +}; + +export type ProgressSections = ProgressSection[]; + /** * For a given list of Zod issues and lessonPlan fields, checks that none of * the errors pertain to the fields. @@ -16,16 +33,6 @@ function getCompleteness(errors: ZodIssue[], fields: string[]) { return !hasErrorInSomeField; } -export type ProgressSections = { - label: string; - key: string; - complete: boolean; -}[]; -type ProgressForDownloads = { - sections: ProgressSections; - totalSections: number; - totalSectionsComplete: number; -}; export function useProgressForDownloads({ lessonPlan, @@ -60,7 +67,8 @@ export function useProgressForDownloads({ return true; } }) ?? []; - const sections = [ + + const sections: ProgressSection[] = [ { label: "Lesson details", key: "title", diff --git a/apps/nextjs/src/components/AppComponents/Chat/chat-left-hand-side.tsx b/apps/nextjs/src/components/AppComponents/Chat/chat-left-hand-side.tsx index d2ff39208..6a4e25561 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/chat-left-hand-side.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/chat-left-hand-side.tsx @@ -26,7 +26,7 @@ const ChatLeftHandSide = ({ demo, isDemoUser, }: Readonly) => { - const { messages, chatAreaRef } = useLessonChat(); + const { chatAreaRef } = useLessonChat(); return ( - { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - inspect && - } + {inspect && } ); } diff --git a/apps/nextjs/src/components/AppComponents/Chat/chat-right-hand-side-lesson.tsx b/apps/nextjs/src/components/AppComponents/Chat/chat-right-hand-side-lesson.tsx index 5be3e7430..8a7fa2b7b 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/chat-right-hand-side-lesson.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/chat-right-hand-side-lesson.tsx @@ -1,5 +1,7 @@ import React, { useRef, useState } from "react"; +import type { LessonPlanKeys } from "@oakai/aila/src/protocol/schema"; + import { useLessonChat } from "@/components/ContextProviders/ChatProvider"; import AiIcon from "../../AiIcon"; @@ -30,7 +32,9 @@ const ChatRightHandSideLesson = ({ const [showScrollButton, setShowScrollButton] = useState(false); // This retains this existing bug, but is fixed on subsequent PRs - const sectionRefs = {}; + const sectionRefs: Partial< + Record> + > = {}; const scrollToBottom = () => { if (chatEndRef.current) { @@ -57,7 +61,7 @@ const ChatRightHandSideLesson = ({ return (
1 && showLessonMobile ? "flex" : "hidden"} fixed bottom-20 left-0 right-0 items-center justify-center duration-150 sm:hidden`} + className={`${messages.length > 1 && showLessonMobile ? "flex" : "hidden"} fixed bottom-20 left-0 right-0 items-center justify-center duration-150 sm:hidden`} > = { parameters: { // Including custom decorators changes the layout from fullscreen layout: "fullscreen", + ...chromaticParams(["mobile", "desktop"]), }, decorators: [ (Story) => ( diff --git a/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/action-button-wrapper.tsx b/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/action-button-wrapper.tsx index 3db3b8c30..447d1f527 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/action-button-wrapper.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/action-button-wrapper.tsx @@ -1,6 +1,7 @@ import { useRef, useState } from "react"; import { getLastAssistantMessage } from "@oakai/aila/src/helpers/chat/getLastAssistantMessage"; +import type { LessonPlanSectionWhileStreaming } from "@oakai/aila/src/protocol/schema"; import { OakBox } from "@oaknational/oak-components"; import type { AilaUserModificationAction } from "@prisma/client"; @@ -18,7 +19,7 @@ import type { FeedbackOption } from "./drop-down-form-wrapper"; export type ActionButtonWrapperProps = Readonly<{ sectionTitle: string; sectionPath: string; - sectionValue: Record | string | Array; + sectionValue: LessonPlanSectionWhileStreaming; options: ModifyOptions | AdditionalMaterialOptions; buttonText: string; actionButtonLabel: string; @@ -59,7 +60,7 @@ const ActionButtonWrapper = ({ chatId: id, messageId: lastAssistantMessage.id, sectionPath, - sectionValue, + sectionValue: String(sectionValue), action: selectedRadio.enumValue, actionOtherText: userFeedbackText || null, }; diff --git a/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/add-additional-materials-button.tsx b/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/add-additional-materials-button.tsx index 23a0353b3..8b2e58468 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/add-additional-materials-button.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/add-additional-materials-button.tsx @@ -1,3 +1,4 @@ +import type { LessonPlanSectionWhileStreaming } from "@oakai/aila/src/protocol/schema"; import type { AilaUserModificationAction } from "@prisma/client"; import ActionButtonWrapper from "./action-button-wrapper"; @@ -7,7 +8,7 @@ import type { FeedbackOption } from "./drop-down-form-wrapper"; export type AdditionalMaterialsProps = Readonly<{ sectionTitle: string; sectionPath: string; - sectionValue: Record | string | Array; + sectionValue: LessonPlanSectionWhileStreaming; }>; const AddAdditionalMaterialsButton = ({ diff --git a/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/chat-section.tsx b/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/chat-section.tsx index e999b74c6..7e1d8b207 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/chat-section.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/chat-section.tsx @@ -1,3 +1,7 @@ +import type { + LessonPlanKeys, + LessonPlanSectionWhileStreaming, +} from "@oakai/aila/src/protocol/schema"; import { sectionToMarkdown } from "@oakai/aila/src/protocol/sectionToMarkdown"; import { OakFlex } from "@oaknational/oak-components"; import { lessonSectionTitlesAndMiniDescriptions } from "data/lessonSectionTitlesAndMiniDescriptions"; @@ -9,17 +13,18 @@ import FlagButton from "./flag-button"; import ModifyButton from "./modify-button"; export type ChatSectionProps = Readonly<{ - objectKey: string; - value: Record | string | Array; + section: LessonPlanKeys; + value: LessonPlanSectionWhileStreaming; }>; -const ChatSection = ({ objectKey, value }: ChatSectionProps) => { + +const ChatSection = ({ section, value }: ChatSectionProps) => { return ( { $position="relative" $display={["none", "flex"]} > - {objectKey === "additionalMaterials" && value === "None" ? ( + {section === "additionalMaterials" && value === "None" ? ( ) : ( )} diff --git a/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/flag-button.tsx b/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/flag-button.tsx index 0254d8ef4..2a98e3c3a 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/flag-button.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/flag-button.tsx @@ -1,6 +1,7 @@ import { useEffect, useRef, useState } from "react"; import { getLastAssistantMessage } from "@oakai/aila/src/helpers/chat/getLastAssistantMessage"; +import type { LessonPlanSectionWhileStreaming } from "@oakai/aila/src/protocol/schema"; import type { AilaUserFlagType } from "@oakai/db"; import { OakBox, OakP, OakRadioGroup } from "@oaknational/oak-components"; import styled from "styled-components"; @@ -26,7 +27,7 @@ type FlagButtonOptions = typeof flagOptions; export type FlagButtonProps = Readonly<{ sectionTitle: string; sectionPath: string; - sectionValue: Record | string | Array; + sectionValue: LessonPlanSectionWhileStreaming; }>; const FlagButton = ({ @@ -48,6 +49,25 @@ const FlagButton = ({ const { mutateAsync } = trpc.chat.chatFeedback.flagSection.useMutation(); + const isPlainObject = (value: unknown): value is Record => { + return typeof value === "object" && value !== null && !Array.isArray(value); + }; + + const prepareSectionValue = ( + value: LessonPlanSectionWhileStreaming, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): string | any[] | Record => { + if ( + typeof value === "string" || + Array.isArray(value) || + isPlainObject(value) + ) { + return value; + } + // For numbers or any other types, convert to string + return String(value); + }; + const flagSectionContent = async () => { if (selectedRadio && lastAssistantMessage) { const payload = { @@ -56,7 +76,7 @@ const FlagButton = ({ flagType: selectedRadio.enumValue, userComment: userFeedbackText, sectionPath, - sectionValue, + sectionValue: prepareSectionValue(sectionValue), }; await mutateAsync(payload); } @@ -93,7 +113,7 @@ const FlagButton = ({ > {flagOptions.map((option) => ( = { component: DropDownSection, tags: ["autodocs"], args: { - objectKey: "learningOutcome", + section: "learningOutcome", value: "I can explain the reasons why frogs are so important to British society and culture", documentContainerRef: { current: null }, @@ -89,7 +89,7 @@ export const Closed: Story = { export const AdditionalMaterials: Story = { args: { - objectKey: "additionalMaterials", + section: "additionalMaterials", value: "None", }, }; @@ -116,7 +116,7 @@ export const ModifyAdditionalMaterials: Story = { }, }, args: { - objectKey: "additionalMaterials", + section: "additionalMaterials", value: "None", }, play: async ({ canvasElement }) => { diff --git a/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/index.tsx b/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/index.tsx index aac7f06e6..15800276d 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/index.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/index.tsx @@ -1,5 +1,6 @@ import { useEffect, useRef, useState } from "react"; +import type { LessonPlanKeys } from "@oakai/aila/src/protocol/schema"; import { camelCaseToSentenceCase } from "@oakai/core/src/utils/camelCaseConversion"; import { OakBox, OakFlex, OakP } from "@oaknational/oak-components"; import { equals } from "ramda"; @@ -15,7 +16,7 @@ import ChatSection from "./chat-section"; const HALF_SECOND = 500; export type DropDownSectionProps = Readonly<{ - objectKey: string; + section: LessonPlanKeys; sectionRefs: Record>; // eslint-disable-next-line @typescript-eslint/no-explicit-any value: any; @@ -26,7 +27,7 @@ export type DropDownSectionProps = Readonly<{ }>; const DropDownSection = ({ - objectKey, + section, sectionRefs, value, documentContainerRef, @@ -35,7 +36,7 @@ const DropDownSection = ({ streamingTimeout = HALF_SECOND, }: DropDownSectionProps) => { const sectionRef = useRef(null); - if (sectionRefs) sectionRefs[objectKey] = sectionRef; + if (sectionRefs) sectionRefs[section] = sectionRef; const [isOpen, setIsOpen] = useState(false); const [status, setStatus] = useState<"empty" | "isStreaming" | "isLoaded">( "empty", @@ -56,7 +57,7 @@ const DropDownSection = ({ setStatus("isStreaming"); if (sectionRef && sectionHasFired === false && status === "isStreaming") { - if (objectKey && value) { + if (section && value) { function scrollToSection() { if (!userHasCancelledAutoScroll) { scrollToRef({ @@ -86,11 +87,12 @@ const DropDownSection = ({ sectionRef, sectionHasFired, status, - objectKey, + section, setIsOpen, prevValue, documentContainerRef, userHasCancelledAutoScroll, + streamingTimeout, ]); return ( @@ -109,7 +111,7 @@ const DropDownSection = ({ setIsOpen(!isOpen)} aria-label="toggle"> - {sectionTitle(objectKey)} + {sectionTitle(section)} @@ -118,7 +120,7 @@ const DropDownSection = ({ {isOpen && (
{status === "isLoaded" ? ( - + ) : (

Loading

diff --git a/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/modify-button.tsx b/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/modify-button.tsx index 427042a61..b3ee39a57 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/modify-button.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/drop-down-section/modify-button.tsx @@ -1,3 +1,4 @@ +import type { LessonPlanSectionWhileStreaming } from "@oakai/aila/src/protocol/schema"; import type { AilaUserModificationAction } from "@prisma/client"; import ActionButtonWrapper from "./action-button-wrapper"; @@ -7,7 +8,7 @@ import type { FeedbackOption } from "./drop-down-form-wrapper"; export type ModifyButtonProps = Readonly<{ sectionTitle: string; sectionPath: string; - sectionValue: Record | string | Array; + sectionValue: LessonPlanSectionWhileStreaming; }>; const ModifyButton = ({ diff --git a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.stories.tsx b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.stories.tsx index 1506c7b4d..db7a114f5 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.stories.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.stories.tsx @@ -38,9 +38,9 @@ export const Default: Story = { learningOutcome: { current: null }, learningCycles: { current: null }, priorKnowledge: { current: null }, - "cycle-1": { current: null }, - "cycle-2": { current: null }, - "cycle-3": { current: null }, + cycle1: { current: null }, + cycle2: { current: null }, + cycle3: { current: null }, }, documentContainerRef: { current: null }, }, diff --git a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.tsx b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.tsx index 98f414d2e..218b19bbe 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.tsx @@ -1,6 +1,9 @@ import React, { useState } from "react"; -import type { LooseLessonPlan } from "@oakai/aila/src/protocol/schema"; +import type { + LessonPlanKeys, + LooseLessonPlan, +} from "@oakai/aila/src/protocol/schema"; import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import { Flex } from "@radix-ui/themes"; @@ -9,10 +12,12 @@ import { scrollToRef } from "@/utils/scrollToRef"; import { useProgressForDownloads } from "../Chat/hooks/useProgressForDownloads"; -type LessonPlanProgressDropdownProps = Readonly<{ +export type LessonPlanProgressDropdownProps = Readonly<{ lessonPlan: LooseLessonPlan; isStreaming: boolean; - sectionRefs: Record>; + sectionRefs: Partial< + Record> + >; documentContainerRef: React.MutableRefObject; }>; @@ -61,10 +66,10 @@ export const LessonPlanProgressDropdown: React.FC< disabled={!complete} className="mb-7 flex gap-6" onClick={() => { - if (key === "cycles" && complete) { - if (sectionRefs["cycle-1"]) { + if (key === "cycle1" && complete) { + if (sectionRefs["cycle1"]) { scrollToRef({ - ref: sectionRefs["cycle-1"], + ref: sectionRefs["cycle1"], containerRef: documentContainerRef, }); } diff --git a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/MobileExportButtons.stories.tsx b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/MobileExportButtons.stories.tsx index b4dad8f06..9d220d9c0 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/MobileExportButtons.stories.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/MobileExportButtons.stories.tsx @@ -4,6 +4,7 @@ import type { ChatContextProps } from "@/components/ContextProviders/ChatProvide import { ChatContext } from "@/components/ContextProviders/ChatProvider"; import { DemoContext } from "@/components/ContextProviders/Demo"; +import { chromaticParams } from "../../../../../.storybook/chromatic"; import { MobileExportButtons } from "./MobileExportButtons"; const ChatDecorator: Story["decorators"] = (Story, { parameters }) => ( @@ -40,6 +41,7 @@ const meta: Meta = { viewport: { defaultViewport: "mobile1", }, + ...chromaticParams(["mobile"]), }, args: { closeMobileLessonPullOut: () => {}, diff --git a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/index.stories.tsx b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/index.stories.tsx index 10df1a045..fa246ab2b 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/index.stories.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/index.stories.tsx @@ -4,6 +4,7 @@ import type { ChatContextProps } from "@/components/ContextProviders/ChatProvide import { ChatContext } from "@/components/ContextProviders/ChatProvider"; import { DemoContext } from "@/components/ContextProviders/Demo"; +import { chromaticParams } from "../../../../../.storybook/chromatic"; import ExportButtons from "./"; const ChatDecorator: Story["decorators"] = (Story, { parameters }) => ( diff --git a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/index.tsx b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/index.tsx index 671e9ee56..e639b2dc1 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/index.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/index.tsx @@ -1,5 +1,6 @@ "use client"; +import type { LessonPlanKeys } from "@oakai/aila/src/protocol/schema"; import { OakSmallSecondaryButton } from "@oaknational/oak-components"; import Link from "next/link"; @@ -11,7 +12,9 @@ import { useDialog } from "../../DialogContext"; import { LessonPlanProgressDropdown } from "./LessonPlanProgressDropdown"; export type ExportButtonsProps = Readonly<{ - sectionRefs: Record>; + sectionRefs: Partial< + Record> + >; documentContainerRef: React.MutableRefObject; }>; @@ -26,7 +29,7 @@ const ExportButtons = ({ const demo = useDemoUser(); return ( -
+
( + + + +); + +const meta: Meta = { + title: "Components/Layout/ChatHeader", + component: Header, + tags: ["autodocs"], + decorators: [DemoDecorator], + parameters: { + layout: "fullscreen", + docs: { + story: { + height: "150px", + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: {}, +}; + +export const DemoUser: Story = { + args: {}, + parameters: { + demoContext: { + isDemoUser: true, + appSessionsPerMonth: 3, + appSessionsRemaining: 2, + }, + ...chromaticParams(["desktop", "desktop-wide"]), + }, +}; + +export const DemoLoading: Story = { + args: {}, + parameters: { + demoContext: { + isDemoUser: true, + appSessionsPerMonth: 3, + appSessionsRemaining: undefined, + }, + }, +}; diff --git a/apps/nextjs/src/components/AppComponents/QuizDesigner/QuizQuestionRow/Distractor.tsx b/apps/nextjs/src/components/AppComponents/QuizDesigner/QuizQuestionRow/Distractor.tsx index 9fc978685..b60358ca2 100644 --- a/apps/nextjs/src/components/AppComponents/QuizDesigner/QuizQuestionRow/Distractor.tsx +++ b/apps/nextjs/src/components/AppComponents/QuizDesigner/QuizQuestionRow/Distractor.tsx @@ -1,3 +1,5 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +// TODO - if we are maintaining the quiz designer, check the hook dependencies import type { Dispatch } from "react"; import { useCallback, useState } from "react"; diff --git a/apps/nextjs/src/components/Header.stories.tsx b/apps/nextjs/src/components/Header.stories.tsx index 0c4c4f43e..6b914c5c9 100644 --- a/apps/nextjs/src/components/Header.stories.tsx +++ b/apps/nextjs/src/components/Header.stories.tsx @@ -6,6 +6,14 @@ const meta: Meta = { title: "Components/Layout/Header", component: Header, tags: ["autodocs"], + parameters: { + layout: "fullscreen", + docs: { + story: { + height: "80px", + }, + }, + }, }; export default meta; diff --git a/apps/nextjs/src/components/Onboarding/AcceptTermsForm.stories.tsx b/apps/nextjs/src/components/Onboarding/AcceptTermsForm.stories.tsx index cca0f90ef..ca173a4d6 100644 --- a/apps/nextjs/src/components/Onboarding/AcceptTermsForm.stories.tsx +++ b/apps/nextjs/src/components/Onboarding/AcceptTermsForm.stories.tsx @@ -1,10 +1,14 @@ import type { Meta, StoryObj } from "@storybook/react"; +import { chromaticParams } from "../../../.storybook/chromatic"; import { AcceptTermsForm } from "./AcceptTermsForm"; const meta: Meta = { title: "Pages/Onboarding/AcceptTermsForm", component: AcceptTermsForm, + parameters: { + ...chromaticParams(["mobile", "desktop"]), + }, }; export default meta; diff --git a/apps/nextjs/src/components/Onboarding/LegacyUpgradeNotice.stories.tsx b/apps/nextjs/src/components/Onboarding/LegacyUpgradeNotice.stories.tsx index d61d4b03f..29b5d1ee6 100644 --- a/apps/nextjs/src/components/Onboarding/LegacyUpgradeNotice.stories.tsx +++ b/apps/nextjs/src/components/Onboarding/LegacyUpgradeNotice.stories.tsx @@ -1,10 +1,14 @@ import type { Meta, StoryObj } from "@storybook/react"; +import { chromaticParams } from "../../../.storybook/chromatic"; import { LegacyUpgradeNotice } from "./LegacyUpgradeNotice"; const meta: Meta = { title: "Pages/Onboarding/LegacyUpgradeNotice", component: LegacyUpgradeNotice, + parameters: { + ...chromaticParams(["mobile", "desktop"]), + }, }; export default meta; diff --git a/apps/nextjs/src/data/lessonSectionTitlesAndMiniDescriptions.ts b/apps/nextjs/src/data/lessonSectionTitlesAndMiniDescriptions.ts index ac9d00013..5f3622c26 100644 --- a/apps/nextjs/src/data/lessonSectionTitlesAndMiniDescriptions.ts +++ b/apps/nextjs/src/data/lessonSectionTitlesAndMiniDescriptions.ts @@ -1,4 +1,24 @@ -export const lessonSectionTitlesAndMiniDescriptions = { +import type { LessonPlanKeys } from "@oakai/aila/src/protocol/schema"; + +export const lessonSectionTitlesAndMiniDescriptions: Record< + LessonPlanKeys, + { description: string } +> = { + title: { + description: "The name of the lesson.", + }, + keyStage: { + description: "The educational stage for which the lesson is intended.", + }, + subject: { + description: "The subject area of the lesson.", + }, + topic: { + description: "An optional topic that this lesson is part of.", + }, + basedOn: { + description: "An Oak lesson that this lesson is based on.", + }, learningOutcome: { description: "A short summary of the knowledge, skills and understanding students are expected to acquire by the end of the lesson.", @@ -47,8 +67,8 @@ export const lessonSectionTitlesAndMiniDescriptions = { description: "Extra resources for further study or practice, including worksheets, readings, and interactive activities.", }, - slides: { - description: - "Visual aids and presentations used throughout the lesson to support teaching and learning.", - }, + // slides: { + // description: + // "Visual aids and presentations used throughout the lesson to support teaching and learning.", + // }, }; diff --git a/package.json b/package.json index a4aa22bb2..f023c4fde 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "db-push": "turbo db-push", "db-push-force": "turbo db-push-force", "db-seed": "turbo db-seed", + "deps": "NODE_OPTIONS=\"--max-old-space-size=8192\" dependency-cruiser --progress --max-depth 10 --output-type err --config .dependency-cruiser.cjs apps packages", + "deps:graph": "NODE_OPTIONS=\"--max-old-space-size=8192\" dependency-cruiser --progress --max-depth 10 --config .dependency-cruiser.cjs --output-type dot apps packages | dot -T svg > dependency-graph.svg", "dev": "FORCE_COLOR=1 turbo dev --parallel --ui=stream --log-prefix=none --filter=!@oakai/db", "doppler:pull:dev": "doppler secrets download --config dev --no-file --format env > .env", "doppler:pull:stg": "doppler secrets download --config stg --no-file --format env > .env", @@ -55,6 +57,7 @@ "@semantic-release/git": "^10.0.1", "@types/jest": "^29.5.14", "autoprefixer": "^10.4.16", + "dependency-cruiser": "^16.7.0", "husky": "^8.0.3", "lint-staged": "^15.2.0", "next": "14.2.18", diff --git a/packages/aila/tsconfig.json b/packages/aila/tsconfig.json index 9f14f1afb..adf689cd4 100644 --- a/packages/aila/tsconfig.json +++ b/packages/aila/tsconfig.json @@ -8,6 +8,7 @@ "../core/src/utils/subjects/index.ts" ], "compilerOptions": { + "baseUrl": "./src", "typeRoots": [ "./../../node_modules/@types", "./src/types", diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 29e64f0c8..024b974bd 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -1,4 +1,7 @@ { "extends": "../../tsconfig.json", - "include": ["src", "index.ts", "transformer.ts"] + "include": ["src", "index.ts", "transformer.ts"], + "compilerOptions": { + "baseUrl": "./src" + } } diff --git a/packages/db/tsconfig.json b/packages/db/tsconfig.json index f45471495..3aff61830 100644 --- a/packages/db/tsconfig.json +++ b/packages/db/tsconfig.json @@ -6,5 +6,8 @@ "client/index.ts", "scripts/**/*.ts", "schemas/**/*.ts" - ] + ], + "compilerOptions": { + "baseUrl": "." + } } diff --git a/packages/eslint-config/src/index.mjs b/packages/eslint-config/src/index.mjs index 737f29472..e8be4fadd 100644 --- a/packages/eslint-config/src/index.mjs +++ b/packages/eslint-config/src/index.mjs @@ -93,7 +93,9 @@ const config = [ rules: { ...rules, - "react/jsx-no-comment-textnodes": "warn" + "react/jsx-no-comment-textnodes": "warn", + "react-hooks/exhaustive-deps": "error", + "react-hooks/rules-of-hooks": "error", }, settings: { "import/resolver": { diff --git a/packages/eslint-config/src/rules.mjs b/packages/eslint-config/src/rules.mjs index a1277ad8f..1aead4601 100644 --- a/packages/eslint-config/src/rules.mjs +++ b/packages/eslint-config/src/rules.mjs @@ -10,7 +10,12 @@ const rulesToMoveToError = { "@typescript-eslint/no-unsafe-argument": "warn", "@typescript-eslint/no-floating-promises": "warn", "@typescript-eslint/no-unsafe-member-access": "warn", - "@typescript-eslint/no-redundant-type-constituents": "warn", + "@typescript-eslint/no-redundant-type-constituents": "warn", +} + +const rulesToDecideOn = { + "@typescript-eslint/no-unsafe-assignment": "off", // this rule is buggy and is causing a lot of false positives + "@typescript-eslint/no-unsafe-call": "off", // this rule is buggy and is causing a lot of false positives } // @ts-check @@ -18,6 +23,7 @@ const rulesToMoveToError = { export const rules = { ...typescript.rules, ...rulesToMoveToError, + ...rulesToDecideOn, "@typescript-eslint/prefer-nullish-coalescing": "warn", "@typescript-eslint/no-unsafe-enum-comparison": "error", "@typescript-eslint/no-unnecessary-type-assertion": "warn", @@ -31,18 +37,16 @@ export const rules = { allowTaggedTemplates: true, }], "@typescript-eslint/require-await": "warn", - "@typescript-eslint/no-unsafe-assignment": "warn", "@typescript-eslint/unbound-method": "off", "@typescript-eslint/restrict-template-expressions": "error", "@typescript-eslint/await-thenable": "error", - "@typescript-eslint/no-unsafe-call": "warn", "@typescript-eslint/explicit-function-return-type": ["off", { allowExpressions: true, allowTypedFunctionExpressions: true, }], // Import plugin rules - "import/no-cycle": "warn", + "import/no-cycle": ["warn", { maxDepth: Infinity }], "import/newline-after-import": "off", "import/no-duplicates": "off", diff --git a/packages/eslint-config/tsconfig.json b/packages/eslint-config/tsconfig.json index c495189a3..9cb69fa06 100644 --- a/packages/eslint-config/tsconfig.json +++ b/packages/eslint-config/tsconfig.json @@ -7,8 +7,16 @@ "outDir": "./dist", "declaration": true, "esModuleInterop": true, - "strict": true + "strict": true, + "baseUrl": "./src" }, - "include": ["*.ts", "src/*.ts", "eslint.config.old", "src/rules.mjs", "src/index.mjs", "src/ignores.mjs"], + "include": [ + "*.ts", + "src/*.ts", + "eslint.config.old", + "src/rules.mjs", + "src/index.mjs", + "src/ignores.mjs" + ], "exclude": ["node_modules", "dist"] -} \ No newline at end of file +} diff --git a/packages/exports/tsconfig.json b/packages/exports/tsconfig.json index 12018590d..b50112251 100644 --- a/packages/exports/tsconfig.json +++ b/packages/exports/tsconfig.json @@ -3,5 +3,8 @@ "ts-node": { "files": true }, - "include": ["src/**/*.ts", "index.ts", "*.ts"] + "include": ["src/**/*.ts", "index.ts", "*.ts"], + "compilerOptions": { + "baseUrl": "./src" + } } diff --git a/packages/ingest/tsconfig.json b/packages/ingest/tsconfig.json index c1e70f700..6731c27c1 100644 --- a/packages/ingest/tsconfig.json +++ b/packages/ingest/tsconfig.json @@ -8,6 +8,7 @@ "esModuleInterop": true, "types": ["jest"], "allowImportingTsExtensions": true, - "moduleDetection": "force" + "moduleDetection": "force", + "baseUrl": "./src" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a0cbf32c8..e624823fc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: autoprefixer: specifier: ^10.4.16 version: 10.4.17(postcss@8.4.35) + dependency-cruiser: + specifier: ^16.7.0 + version: 16.7.0 husky: specifier: ^8.0.3 version: 8.0.3 @@ -1139,14 +1142,14 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/highlight': 7.24.5 - picocolors: 1.0.1 + picocolors: 1.1.1 /@babel/code-frame@7.24.7: resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} engines: {node: '>=6.9.0'} dependencies: '@babel/highlight': 7.24.7 - picocolors: 1.0.1 + picocolors: 1.1.1 /@babel/code-frame@7.26.2: resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} @@ -1536,7 +1539,7 @@ packages: '@babel/helper-validator-identifier': 7.25.9 chalk: 2.4.2 js-tokens: 4.0.0 - picocolors: 1.0.1 + picocolors: 1.1.1 /@babel/highlight@7.24.7: resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} @@ -1545,7 +1548,7 @@ packages: '@babel/helper-validator-identifier': 7.25.9 chalk: 2.4.2 js-tokens: 4.0.0 - picocolors: 1.0.1 + picocolors: 1.1.1 /@babel/parser@7.25.3: resolution: {integrity: sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==} @@ -10220,13 +10223,23 @@ packages: dependencies: acorn: 8.14.0 + /acorn-jsx-walk@2.0.0: + resolution: {integrity: sha512-uuo6iJj4D4ygkdzd6jPtcxs8vZgDX9YFIkqczGImoypX2fQ4dVImmu3UzA4ynixCIMTrEOWW+95M2HuBaCEOVA==} + dev: false + /acorn-jsx@5.3.2(acorn@8.14.0): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: acorn: 8.14.0 - dev: true + + /acorn-loose@8.4.0: + resolution: {integrity: sha512-M0EUka6rb+QC4l9Z3T0nJEzNOO7JcoJlYMrBlyBCiFSXRyxjLKayd4TbQs2FDRWQU1h9FR7QVNHt+PEaoNL5rQ==} + engines: {node: '>=0.4.0'} + dependencies: + acorn: 8.14.0 + dev: false /acorn-typescript@1.4.13(acorn@8.14.0): resolution: {integrity: sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==} @@ -10240,6 +10253,13 @@ packages: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} engines: {node: '>=0.4.0'} + /acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + dependencies: + acorn: 8.14.0 + dev: false + /acorn@8.11.3: resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} engines: {node: '>=0.4.0'} @@ -10400,7 +10420,6 @@ packages: fast-uri: 3.0.1 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - dev: true /american-british-english-translator@0.2.1: resolution: {integrity: sha512-G+RQd2Hn4cfsVrA0QRGOZYp4LgU5SIMTNBOWXI2FKMFljixGbMcO7oAXjwRZfzJ0iJJbAeLEGNwpTH4xSbQpLA==} @@ -11960,6 +11979,11 @@ packages: engines: {node: '>=18'} dev: false + /commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + dev: false + /commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -12837,6 +12861,35 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + /dependency-cruiser@16.7.0: + resolution: {integrity: sha512-522LLjHINl9r0RIZ8/6s6TqIHTuEJG3XDU2WPSm9dG0rvLUYVyQwE9ID31tDFs4OOyEhdOPaqAaAG1jRv/Zwbg==} + engines: {node: ^18.17||>=20} + hasBin: true + dependencies: + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + acorn-jsx-walk: 2.0.0 + acorn-loose: 8.4.0 + acorn-walk: 8.3.4 + ajv: 8.17.1 + commander: 12.1.0 + enhanced-resolve: 5.17.1 + ignore: 6.0.2 + interpret: 3.1.1 + is-installed-globally: 1.0.0 + json5: 2.2.3 + memoize: 10.0.0 + picocolors: 1.1.1 + picomatch: 4.0.2 + prompts: 2.4.2 + rechoir: 0.8.0 + safe-regex: 2.1.1 + semver: 7.6.3 + teamcity-service-messages: 0.1.14 + tsconfig-paths-webpack-plugin: 4.2.0 + watskeburt: 4.2.2 + dev: false + /dependency-graph@0.11.0: resolution: {integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==} engines: {node: '>= 0.6.0'} @@ -14156,7 +14209,6 @@ packages: /fast-uri@3.0.1: resolution: {integrity: sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==} - dev: true /fast-url-parser@1.1.3: resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==} @@ -14778,6 +14830,13 @@ packages: minipass: 4.2.8 path-scurry: 1.11.1 + /global-directory@4.0.1: + resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} + engines: {node: '>=18'} + dependencies: + ini: 4.1.1 + dev: false + /global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} engines: {node: '>=10'} @@ -15461,6 +15520,11 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + /ignore@6.0.2: + resolution: {integrity: sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==} + engines: {node: '>= 4'} + dev: false + /image-size@1.0.2: resolution: {integrity: sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==} engines: {node: '>=14.0.0'} @@ -15561,6 +15625,11 @@ packages: engines: {node: '>=10'} dev: true + /ini@4.1.1: + resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: false + /inline-style-parser@0.1.1: resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} dev: false @@ -15680,6 +15749,11 @@ packages: hasown: 2.0.2 side-channel: 1.0.4 + /interpret@3.1.1: + resolution: {integrity: sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==} + engines: {node: '>=10.13.0'} + dev: false + /into-stream@7.0.0: resolution: {integrity: sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==} engines: {node: '>=12'} @@ -15921,6 +15995,14 @@ packages: is-path-inside: 3.0.3 dev: true + /is-installed-globally@1.0.0: + resolution: {integrity: sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==} + engines: {node: '>=18'} + dependencies: + global-directory: 4.0.1 + is-path-inside: 4.0.0 + dev: false + /is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} @@ -15992,6 +16074,11 @@ packages: engines: {node: '>=8'} dev: true + /is-path-inside@4.0.0: + resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} + engines: {node: '>=12'} + dev: false + /is-plain-obj@1.1.0: resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} engines: {node: '>=0.10.0'} @@ -16903,7 +16990,6 @@ packages: /json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - dev: true /json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} @@ -17052,7 +17138,6 @@ packages: /kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - dev: true /koalas@1.0.2: resolution: {integrity: sha512-RYhBbYaTTTHId3l6fnMZc3eGQNW6FVCqMG6AMwA5I1Mafr6AflaXeoi6x3xQuATRotGYRLk6+1ELZH4dstFNOA==} @@ -18072,6 +18157,13 @@ packages: fs-monkey: 1.0.6 dev: true + /memoize@10.0.0: + resolution: {integrity: sha512-H6cBLgsi6vMWOcCpvVCdFFnl3kerEXbrYh9q+lY6VXvQSmM6CkmV08VOwT+WE2tzIEqRPFfAq3fm4v/UIW6mSA==} + engines: {node: '>=18'} + dependencies: + mimic-function: 5.0.1 + dev: false + /memoizerific@1.11.3: resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==} dependencies: @@ -18428,6 +18520,11 @@ packages: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} + /mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + dev: false + /mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -19823,9 +19920,6 @@ packages: /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - /picocolors@1.0.1: - resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} - /picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -19833,6 +19927,11 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + /picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + dev: false + /pidtree@0.6.0: resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} engines: {node: '>=0.10'} @@ -20118,7 +20217,7 @@ packages: engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.7 - picocolors: 1.0.1 + picocolors: 1.1.1 source-map-js: 1.2.0 /postcss@8.4.35: @@ -20134,7 +20233,7 @@ packages: engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.7 - picocolors: 1.0.1 + picocolors: 1.1.1 source-map-js: 1.2.0 dev: true @@ -20362,7 +20461,6 @@ packages: dependencies: kleur: 3.0.3 sisteransi: 1.0.5 - dev: true /prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -20906,6 +21004,13 @@ packages: tiny-invariant: 1.3.3 tslib: 2.8.1 + /rechoir@0.8.0: + resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==} + engines: {node: '>= 10.13.0'} + dependencies: + resolve: 1.22.8 + dev: false + /redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -20972,6 +21077,11 @@ packages: resolution: {integrity: sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==} dev: true + /regexp-tree@0.1.27: + resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} + hasBin: true + dev: false + /regexp.prototype.flags@1.5.2: resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} engines: {node: '>= 0.4'} @@ -21102,7 +21212,6 @@ packages: /require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - dev: true /require-in-the-middle@7.3.0: resolution: {integrity: sha512-nQFEv9gRw6SJAwWD2LrL0NmQvAcO7FBwJbwmr2ttPAacfy0xuiOjE5zt+zM4xDyuyvUaxBi/9gb2SoCyNEVJcw==} @@ -21339,6 +21448,12 @@ packages: is-regex: 1.1.4 dev: true + /safe-regex@2.1.1: + resolution: {integrity: sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==} + dependencies: + regexp-tree: 0.1.27 + dev: false + /safe-stable-stringify@2.4.3: resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} engines: {node: '>=10'} @@ -21722,7 +21837,6 @@ packages: /sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - dev: true /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} @@ -22626,6 +22740,10 @@ packages: yallist: 4.0.0 dev: true + /teamcity-service-messages@0.1.14: + resolution: {integrity: sha512-29aQwaHqm8RMX74u2o/h1KbMLP89FjNiMxD9wbF2BbWOnbM+q+d1sCEC+MqCc4QW3NJykn77OMpTFw/xTHIc0w==} + dev: false + /teeny-request@9.0.0: resolution: {integrity: sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==} engines: {node: '>=14'} @@ -23065,6 +23183,16 @@ packages: tsconfig-paths: 4.2.0 dev: true + /tsconfig-paths-webpack-plugin@4.2.0: + resolution: {integrity: sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==} + engines: {node: '>=10.13.0'} + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.17.1 + tapable: 2.2.1 + tsconfig-paths: 4.2.0 + dev: false + /tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} dependencies: @@ -23081,7 +23209,6 @@ packages: json5: 2.2.3 minimist: 1.2.8 strip-bom: 3.0.0 - dev: true /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} @@ -23570,7 +23697,7 @@ packages: dependencies: browserslist: 4.23.0 escalade: 3.1.1 - picocolors: 1.0.1 + picocolors: 1.1.1 /update-browserslist-db@1.1.0(browserslist@4.23.3): resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} @@ -23889,6 +24016,12 @@ packages: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 + /watskeburt@4.2.2: + resolution: {integrity: sha512-AOCg1UYxWpiHW1tUwqpJau8vzarZYTtzl2uu99UptBmbzx6kOzCGMfRLF6KIRX4PYekmryn89MzxlRNkL66YyA==} + engines: {node: ^18||>=20} + hasBin: true + dev: false + /wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} dependencies: diff --git a/release.config.js b/release.config.cjs similarity index 100% rename from release.config.js rename to release.config.cjs