From c5f1542fe5841fc98c20d208919c03ae96dedad7 Mon Sep 17 00:00:00 2001 From: Jiwon Kwon Date: Tue, 8 Sep 2026 23:25:21 +0900 Subject: [PATCH 1/7] Add actor generation form I implemented the actor-generation route with Formisch and Valibot validation, Kobalte controls, Relay global-ID mutation input, loading state, and distinct GraphQL, domain, and network error handling. Codex reviewed and explained the Relay and generated-type behavior, then made focused edits to route Kobalte numeric values into Formisch, correct a generated import suffix and typo, and resolve lint findings. I retained the design and implementation decisions. Codex ran the full repository check successfully. I will manually verify the UI before submitting the pull request. Assisted-by: Codex:gpt-5.6-sol --- .../workspace/create/[instance_id]/actors.tsx | 173 ++++++++++++++++-- 1 file changed, 161 insertions(+), 12 deletions(-) diff --git a/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx b/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx index 5483a43..b2d66f9 100644 --- a/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx +++ b/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx @@ -14,6 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +import { Field, Form, type SubmitHandler, createForm } from "@formisch/solid"; import { Button } from "@kobalte/core/button"; import { NumberField } from "@kobalte/core/number-field"; import { Title } from "@solidjs/meta"; @@ -23,13 +24,17 @@ import { query, } from "@solidjs/router"; import { graphql } from "relay-runtime"; +import { Show, createSignal } from "solid-js"; import { + createMutation, createPreloadedQuery, loadQuery, useRelayEnvironment, } from "solid-relay"; +import * as v from "valibot"; -import type { GetHostQuery } from "./__generated__/GetHostQuery.graphql.tsx"; +import type { GenerateActorsMutation } from "./__generated__/GenerateActorsMutation.graphql.ts"; +import type { GetHostQuery } from "./__generated__/GetHostQuery.graphql.ts"; import styles from "~/styles/form.module.css"; @@ -37,6 +42,7 @@ const getHostQuery = graphql` query GetHostQuery($instanceId: ID!) { instance: node(id: $instanceId) { ... on Instance { + id host } } @@ -62,12 +68,119 @@ export const route = { type RouteData = ReturnType; +const generateActorsMutation = graphql` + mutation GenerateActorsMutation($instance: ID!, $size: Int!) { + generateActors(instance: $instance, size: $size) { + resultType: __typename + ... on CreateActorsSuccess { + actors { + id + uuid + username + handle + iri + } + } + ... on CreateActorsError { + type + message + } + } + } +`; +const generateActorsSchema = v.object({ + size: v.pipe( + v.number("Enter the number of actors."), + v.integer("The number of actors must be a whole number."), + v.minValue(1, "Create at least one actor."), + ), +}); + export default function CreateActorsPage(props: RouteSectionProps) { + const [errorMessage, setErrorMessage] = createSignal(); + const [commitGenerateActors, isGeneratingActors] = + createMutation(generateActorsMutation); + const data = createPreloadedQuery( getHostQuery, () => props.data, ); + const generateActorsForm = createForm({ + schema: generateActorsSchema, + initialInput: { + size: 1, + }, + }); + + const submit: SubmitHandler = ({ size }) => { + const instance = data()?.instance?.id; + + if (instance === undefined) { + setErrorMessage("Instance not found."); + return; + } + setErrorMessage(undefined); + commitGenerateActors({ + variables: { instance, size }, + onCompleted: (response, errors) => { + const graphQLErrors = errors ?? []; + if (graphQLErrors.length > 0) { + setErrorMessage( + graphQLErrors.map((error) => error.message).join("\n"), + ); + return; + } + + const result = response.generateActors; + switch (result.resultType) { + case "CreateActorsSuccess": { + //FIXME: pop up success message + return; + } + case "CreateActorsError": { + switch (result.type) { + case "InvalidSize": { + setErrorMessage("Enter at least one actor."); + return; + } + case "InstanceNotFound": { + setErrorMessage( + "The selected instance could not be found or is no longer available.", + ); + return; + } + case "TooManyActors": { + setErrorMessage(result.message); + return; + } + case "%future added value": { + setErrorMessage( + "The server returned an unsupported actor-generation error.", + ); + return; + } + default: { + setErrorMessage("Unable to generate actors."); + return; + } + } + } + case "%other": { + setErrorMessage("Unable to generate actors."); + return; + } + default: { + setErrorMessage("Unable to generate actors."); + } + } + }, + onError: (error) => { + setErrorMessage(error.message); + }, + }); + }; + return (
Create Actors @@ -76,19 +189,55 @@ export default function CreateActorsPage(props: RouteSectionProps) {

Create Actors

Selected Instance: {data()?.instance?.host}

-
- - The Number of Actors - - - DrFed generates actors in {data()?.instance?.host}. - - - - -
+ + + +
); From a190b7700f1feb45f4f9dc3e63df4becd226811a Mon Sep 17 00:00:00 2001 From: Jiwon Kwon Date: Wed, 9 Sep 2026 13:11:19 +0900 Subject: [PATCH 2/7] Pin Seroval for Relay route preloading I pinned Seroval 1.5.4 in the web package so Solid Relay uses the same OpaqueReference implementation as Solid SSR, preventing serialization failures when entering preloaded dynamic routes. Codex diagnosed the upstream peer-version mismatch and applied the dependency and lockfile changes. I confirmed the fix in the development environment by entering the instance route and observing its GraphQL query execute normally. Codex ran mise run check, mise run build, mise run test, and an SSR request to the affected dynamic route successfully. Assisted-by: Codex:gpt-5.6-sol --- packages/web/package.json | 3 +- pnpm-lock.yaml | 137 +++++++++++++++++++------------------- 2 files changed, 71 insertions(+), 69 deletions(-) diff --git a/packages/web/package.json b/packages/web/package.json index 32c3936..8199551 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -46,13 +46,14 @@ "preview": "vite preview" }, "dependencies": { - "@kobalte/core": "^0.13.13", "@formisch/solid": "1.0.0", + "@kobalte/core": "^0.13.13", "@solidjs/meta": "^0.29.4", "@solidjs/router": "^1.0.0", "@solidjs/start": "^2.0.0", "nitro": "3.0.260610-beta", "relay-runtime": "^21.0.1", + "seroval": "1.5.4", "solid-js": "^1.9.5", "solid-relay": "1.0.0-beta.29", "valibot": "1.4.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dce17f7..1fbd89d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -289,19 +289,22 @@ importers: version: 1.0.0(solid-js@1.9.14) '@solidjs/start': specifier: ^2.0.0 - version: 2.0.0(@solidjs/router@1.0.0(solid-js@1.9.14))(crossws@0.4.10(srvx@0.11.16))(supports-color@7.2.0)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + version: 2.0.0(@solidjs/router@1.0.0(solid-js@1.9.14))(crossws@0.4.10(srvx@0.11.16))(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) nitro: specifier: 3.0.260610-beta - version: 3.0.260610-beta(@electric-sql/pglite@0.5.3)(chokidar@5.0.0)(dotenv@17.4.2)(drizzle-orm@1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.9)(valibot@1.4.2(typescript@7.0.2)))(giget@3.3.0)(ioredis@5.11.1(supports-color@7.2.0))(jiti@2.7.0)(lru-cache@11.5.1)(rollup@4.62.2)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + version: 3.0.260610-beta(@electric-sql/pglite@0.5.3)(chokidar@5.0.0)(dotenv@17.4.2)(drizzle-orm@1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.9)(valibot@1.4.2(typescript@7.0.2)))(giget@3.3.0)(ioredis@5.11.1)(jiti@2.7.0)(lru-cache@11.5.1)(rollup@4.62.2)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) relay-runtime: specifier: ^21.0.1 version: 21.0.1 + seroval: + specifier: 1.5.4 + version: 1.5.4 solid-js: specifier: ^1.9.5 version: 1.9.14 solid-relay: specifier: 1.0.0-beta.29 - version: 1.0.0-beta.29(relay-runtime@21.0.1)(seroval@1.6.0)(solid-js@1.9.14) + version: 1.0.0-beta.29(relay-runtime@21.0.1)(seroval@1.5.4)(solid-js@1.9.14) valibot: specifier: 1.4.2 version: 1.4.2(typescript@7.0.2) @@ -317,7 +320,7 @@ importers: version: 20.1.1 eslint-plugin-solid: specifier: ^0.14.5 - version: 0.14.5(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@7.0.2) + version: 0.14.5(eslint@9.39.4(jiti@2.7.0))(typescript@7.0.2) relay-compiler: specifier: ^21.0.1 version: 21.0.1 @@ -3909,20 +3912,20 @@ snapshots: '@babel/compat-data@7.29.7': {} - '@babel/core@7.29.7(supports-color@7.2.0)': + '@babel/core@7.29.7': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.8 '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) '@babel/helpers': 7.29.7 '@babel/parser': 7.29.8 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.8(supports-color@7.2.0) + '@babel/traverse': 7.29.8 '@babel/types': 7.29.8 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -3951,19 +3954,19 @@ snapshots: dependencies: '@babel/types': 7.29.8 - '@babel/helper-module-imports@7.29.7(supports-color@7.2.0)': + '@babel/helper-module-imports@7.29.7': dependencies: - '@babel/traverse': 7.29.8(supports-color@7.2.0) + '@babel/traverse': 7.29.8 '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/helper-module-imports': 7.29.7(supports-color@7.2.0) + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.8(supports-color@7.2.0) + '@babel/traverse': 7.29.8 transitivePeerDependencies: - supports-color @@ -3984,9 +3987,9 @@ snapshots: dependencies: '@babel/types': 7.29.8 - '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 '@babel/runtime@7.29.7': {} @@ -3997,7 +4000,7 @@ snapshots: '@babel/parser': 7.29.8 '@babel/types': 7.29.8 - '@babel/traverse@7.29.8(supports-color@7.2.0)': + '@babel/traverse@7.29.8': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.8 @@ -4005,7 +4008,7 @@ snapshots: '@babel/parser': 7.29.8 '@babel/template': 7.29.7 '@babel/types': 7.29.8 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -4247,17 +4250,17 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))': + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.7.0))': dependencies: - eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0) + eslint: 9.39.4(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.21.2(supports-color@7.2.0)': + '@eslint/config-array@0.21.2': dependencies: '@eslint/object-schema': 2.1.7 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 minimatch: 3.1.5 transitivePeerDependencies: - supports-color @@ -4270,10 +4273,10 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.5(supports-color@7.2.0)': + '@eslint/eslintrc@3.3.5': dependencies: ajv: 6.15.0 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -5037,10 +5040,10 @@ snapshots: dependencies: solid-js: 1.9.14 - '@solidjs/start@2.0.0(@solidjs/router@1.0.0(solid-js@1.9.14))(crossws@0.4.10(srvx@0.11.16))(supports-color@7.2.0)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))': + '@solidjs/start@2.0.0(@solidjs/router@1.0.0(solid-js@1.9.14))(crossws@0.4.10(srvx@0.11.16))(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))': dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - '@babel/traverse': 7.29.8(supports-color@7.2.0) + '@babel/core': 7.29.7 + '@babel/traverse': 7.29.8 '@babel/types': 7.29.8 '@solidjs/meta': 0.29.4(solid-js@1.9.14) '@types/babel__traverse': 7.28.0 @@ -5064,7 +5067,7 @@ snapshots: srvx: 0.12.5 terracotta: 1.1.1(solid-js@1.9.14) vite: 8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) - vite-plugin-solid: 2.11.14(solid-js@1.9.14)(supports-color@7.2.0)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + vite-plugin-solid: 2.11.14(solid-js@1.9.14)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) optionalDependencies: '@solidjs/router': 1.0.0(solid-js@1.9.14) transitivePeerDependencies: @@ -5139,11 +5142,11 @@ snapshots: '@types/unist@3.0.3': {} - '@typescript-eslint/project-service@8.62.1(supports-color@7.2.0)(typescript@7.0.2)': + '@typescript-eslint/project-service@8.62.1(typescript@7.0.2)': dependencies: '@typescript-eslint/tsconfig-utils': 8.62.1(typescript@7.0.2) '@typescript-eslint/types': 8.62.1 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 typescript: 7.0.2 transitivePeerDependencies: - supports-color @@ -5159,13 +5162,13 @@ snapshots: '@typescript-eslint/types@8.62.1': {} - '@typescript-eslint/typescript-estree@8.62.1(supports-color@7.2.0)(typescript@7.0.2)': + '@typescript-eslint/typescript-estree@8.62.1(typescript@7.0.2)': dependencies: - '@typescript-eslint/project-service': 8.62.1(supports-color@7.2.0)(typescript@7.0.2) + '@typescript-eslint/project-service': 8.62.1(typescript@7.0.2) '@typescript-eslint/tsconfig-utils': 8.62.1(typescript@7.0.2) '@typescript-eslint/types': 8.62.1 '@typescript-eslint/visitor-keys': 8.62.1 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 minimatch: 10.2.5 semver: 7.8.4 tinyglobby: 0.2.17 @@ -5174,13 +5177,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.62.1(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@7.0.2)': + '@typescript-eslint/utils@8.62.1(eslint@9.39.4(jiti@2.7.0))(typescript@7.0.2)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.62.1 '@typescript-eslint/types': 8.62.1 - '@typescript-eslint/typescript-estree': 8.62.1(supports-color@7.2.0)(typescript@7.0.2) - eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0) + '@typescript-eslint/typescript-estree': 8.62.1(typescript@7.0.2) + eslint: 9.39.4(jiti@2.7.0) typescript: 7.0.2 transitivePeerDependencies: - supports-color @@ -5401,19 +5404,19 @@ snapshots: pvutils: 1.2.0 tslib: 2.8.1 - babel-plugin-jsx-dom-expressions@0.40.7(@babel/core@7.29.7(supports-color@7.2.0)): + babel-plugin-jsx-dom-expressions@0.40.7(@babel/core@7.29.7): dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/core': 7.29.7 '@babel/helper-module-imports': 7.18.6 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) '@babel/types': 7.29.8 html-entities: 2.3.3 parse5: 7.3.0 - babel-preset-solid@1.9.12(@babel/core@7.29.7(supports-color@7.2.0))(solid-js@1.9.14): + babel-preset-solid@1.9.12(@babel/core@7.29.7)(solid-js@1.9.14): dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) - babel-plugin-jsx-dom-expressions: 0.40.7(@babel/core@7.29.7(supports-color@7.2.0)) + '@babel/core': 7.29.7 + babel-plugin-jsx-dom-expressions: 0.40.7(@babel/core@7.29.7) optionalDependencies: solid-js: 1.9.14 @@ -5537,11 +5540,9 @@ snapshots: '@electric-sql/pglite': 0.5.3 drizzle-orm: 1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.9)(valibot@1.4.2(typescript@7.0.2)) - debug@4.4.3(supports-color@7.2.0): + debug@4.4.3: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 7.2.0 deep-is@0.1.4: {} @@ -5670,10 +5671,10 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-solid@0.14.5(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@7.0.2): + eslint-plugin-solid@0.14.5(eslint@9.39.4(jiti@2.7.0))(typescript@7.0.2): dependencies: - '@typescript-eslint/utils': 8.62.1(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@7.0.2) - eslint: 9.39.4(jiti@2.7.0)(supports-color@7.2.0) + '@typescript-eslint/utils': 8.62.1(eslint@9.39.4(jiti@2.7.0))(typescript@7.0.2) + eslint: 9.39.4(jiti@2.7.0) estraverse: 5.3.0 is-html: 2.0.0 kebab-case: 1.0.2 @@ -5694,14 +5695,14 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0): + eslint@9.39.4(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0)(supports-color@7.2.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.2(supports-color@7.2.0) + '@eslint/config-array': 0.21.2 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.5(supports-color@7.2.0) + '@eslint/eslintrc': 3.3.5 '@eslint/js': 9.39.4 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.8 @@ -5711,7 +5712,7 @@ snapshots: ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -5950,11 +5951,11 @@ snapshots: dependencies: loose-envify: 1.4.0 - ioredis@5.11.1(supports-color@7.2.0): + ioredis@5.11.1: dependencies: '@ioredis/commands': 1.10.0 cluster-key-slot: 1.1.1 - debug: 4.4.3(supports-color@7.2.0) + debug: 4.4.3 denque: 2.1.0 redis-errors: 1.2.0 redis-parser: 3.0.0 @@ -6172,7 +6173,7 @@ snapshots: nf3@0.3.23: {} - nitro@3.0.260610-beta(@electric-sql/pglite@0.5.3)(chokidar@5.0.0)(dotenv@17.4.2)(drizzle-orm@1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.9)(valibot@1.4.2(typescript@7.0.2)))(giget@3.3.0)(ioredis@5.11.1(supports-color@7.2.0))(jiti@2.7.0)(lru-cache@11.5.1)(rollup@4.62.2)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)): + nitro@3.0.260610-beta(@electric-sql/pglite@0.5.3)(chokidar@5.0.0)(dotenv@17.4.2)(drizzle-orm@1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.9)(valibot@1.4.2(typescript@7.0.2)))(giget@3.3.0)(ioredis@5.11.1)(jiti@2.7.0)(lru-cache@11.5.1)(rollup@4.62.2)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)): dependencies: consola: 3.4.2 crossws: 0.4.10(srvx@0.11.16) @@ -6187,7 +6188,7 @@ snapshots: rolldown: 1.2.0 srvx: 0.11.16 unenv: 2.0.0-rc.24 - unstorage: 2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.5.3)(drizzle-orm@1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.9)(valibot@1.4.2(typescript@7.0.2))))(ioredis@5.11.1(supports-color@7.2.0))(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3) + unstorage: 2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.5.3)(drizzle-orm@1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.9)(valibot@1.4.2(typescript@7.0.2))))(ioredis@5.11.1)(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3) optionalDependencies: dotenv: 17.4.2 giget: 3.3.0 @@ -6628,20 +6629,20 @@ snapshots: '@corvu/utils': 0.4.2(solid-js@1.9.14) solid-js: 1.9.14 - solid-refresh@0.6.3(solid-js@1.9.14)(supports-color@7.2.0): + solid-refresh@0.6.3(solid-js@1.9.14): dependencies: '@babel/generator': 7.29.8 - '@babel/helper-module-imports': 7.29.7(supports-color@7.2.0) + '@babel/helper-module-imports': 7.29.7 '@babel/types': 7.29.8 solid-js: 1.9.14 transitivePeerDependencies: - supports-color - solid-relay@1.0.0-beta.29(relay-runtime@21.0.1)(seroval@1.6.0)(solid-js@1.9.14): + solid-relay@1.0.0-beta.29(relay-runtime@21.0.1)(seroval@1.5.4)(solid-js@1.9.14): dependencies: dequal: 2.0.3 relay-runtime: 21.0.1 - seroval: 1.6.0 + seroval: 1.5.4 solid-js: 1.9.14 tiny-invariant: 1.3.3 @@ -6842,11 +6843,11 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 - unstorage@2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.5.3)(drizzle-orm@1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.9)(valibot@1.4.2(typescript@7.0.2))))(ioredis@5.11.1(supports-color@7.2.0))(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3): + unstorage@2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.5.3)(drizzle-orm@1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.9)(valibot@1.4.2(typescript@7.0.2))))(ioredis@5.11.1)(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3): optionalDependencies: chokidar: 5.0.0 db0: 0.3.4(@electric-sql/pglite@0.5.3)(drizzle-orm@1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.9)(valibot@1.4.2(typescript@7.0.2))) - ioredis: 5.11.1(supports-color@7.2.0) + ioredis: 5.11.1 lru-cache: 11.5.1 ofetch: 2.0.0-alpha.3 @@ -6898,14 +6899,14 @@ snapshots: transitivePeerDependencies: - typescript - vite-plugin-solid@2.11.14(solid-js@1.9.14)(supports-color@7.2.0)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)): + vite-plugin-solid@2.11.14(solid-js@1.9.14)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)): dependencies: - '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/core': 7.29.7 '@types/babel__core': 7.20.5 - babel-preset-solid: 1.9.12(@babel/core@7.29.7(supports-color@7.2.0))(solid-js@1.9.14) + babel-preset-solid: 1.9.12(@babel/core@7.29.7)(solid-js@1.9.14) merge-anything: 5.1.7 solid-js: 1.9.14 - solid-refresh: 0.6.3(solid-js@1.9.14)(supports-color@7.2.0) + solid-refresh: 0.6.3(solid-js@1.9.14) vite: 8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) vitefu: 1.1.3(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) transitivePeerDependencies: From a42ee182fefb28fb64f46a5691133c50a18c2c15 Mon Sep 17 00:00:00 2001 From: Jiwon Kwon Date: Fri, 11 Sep 2026 11:48:01 +0900 Subject: [PATCH 3/7] Return to previous page after successful query response --- .../web/src/routes/workspace/create/[instance_id]/actors.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx b/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx index b2d66f9..bb465df 100644 --- a/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx +++ b/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx @@ -22,6 +22,7 @@ import { type RouteDefinition, type RouteSectionProps, query, + useNavigate, } from "@solidjs/router"; import { graphql } from "relay-runtime"; import { Show, createSignal } from "solid-js"; @@ -100,6 +101,7 @@ export default function CreateActorsPage(props: RouteSectionProps) { const [errorMessage, setErrorMessage] = createSignal(); const [commitGenerateActors, isGeneratingActors] = createMutation(generateActorsMutation); + const navigate = useNavigate(); const data = createPreloadedQuery( getHostQuery, @@ -135,7 +137,7 @@ export default function CreateActorsPage(props: RouteSectionProps) { const result = response.generateActors; switch (result.resultType) { case "CreateActorsSuccess": { - //FIXME: pop up success message + navigate(-1); return; } case "CreateActorsError": { From 9b3a3b001f619c9823d3d1e45c73aa85bc35c99d Mon Sep 17 00:00:00 2001 From: Jiwon Kwon Date: Sat, 12 Sep 2026 17:57:49 +0900 Subject: [PATCH 4/7] Fix actor creation navigation, success toast, and empty input Navigate to the selected instance by slug after actor creation and refresh its cached actor list. Add a persistent Kobalte toast region with a dismissible success notification. Use the number field rawValue prop so clearing the actor count leaves the input editable. Codex generated these changes at my request. I verified that the toast worked and reported the empty-input NaN bug, which guided the subsequent input fix. The input fix has not been browser-verified. Validation: mise run check and mise run test (including the build) passed; lint reports a callback statement-count warning. Assisted-by: Codex:gpt-6-astra --- packages/web/src/app.tsx | 2 + packages/web/src/components/Toast.tsx | 41 ++++++++++++ .../workspace/create/[instance_id]/actors.tsx | 25 +++++-- packages/web/src/styles/toast.module.css | 66 +++++++++++++++++++ 4 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 packages/web/src/components/Toast.tsx create mode 100644 packages/web/src/styles/toast.module.css diff --git a/packages/web/src/app.tsx b/packages/web/src/app.tsx index 0ce0c5f..7efa801 100644 --- a/packages/web/src/app.tsx +++ b/packages/web/src/app.tsx @@ -24,6 +24,7 @@ import { Suspense } from "solid-js"; import { RelayEnvironmentProvider } from "solid-relay"; import { HeaderAccountButton } from "./components/HeaderAccountButton.tsx"; +import { ToastRegion } from "./components/Toast.tsx"; import { createRelayEnvironment } from "./RelayEnvironment.ts"; import styles from "./styles/app.module.css"; @@ -37,6 +38,7 @@ export default function App() { root={(props) => ( DrFed +
diff --git a/packages/web/src/components/Toast.tsx b/packages/web/src/components/Toast.tsx new file mode 100644 index 0000000..dde293e --- /dev/null +++ b/packages/web/src/components/Toast.tsx @@ -0,0 +1,41 @@ +// DrFed: A web-based platform for developing and debugging ActivityPub apps +// Copyright (C) 2026 DrFed team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import { Toast, toaster } from "@kobalte/core/toast"; +import { Portal } from "solid-js/web"; + +import styles from "~/styles/toast.module.css"; + +export function ToastRegion() { + return ( + + + + + + ); +} + +export function showSuccessToast(message: string): number { + return toaster.show((props) => ( + + {message} + + + + + )); +} diff --git a/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx b/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx index bb465df..1677b9c 100644 --- a/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx +++ b/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx @@ -22,6 +22,7 @@ import { type RouteDefinition, type RouteSectionProps, query, + revalidate, useNavigate, } from "@solidjs/router"; import { graphql } from "relay-runtime"; @@ -34,6 +35,8 @@ import { } from "solid-relay"; import * as v from "valibot"; +import { showSuccessToast } from "~/components/Toast.tsx"; + import type { GenerateActorsMutation } from "./__generated__/GenerateActorsMutation.graphql.ts"; import type { GetHostQuery } from "./__generated__/GetHostQuery.graphql.ts"; @@ -45,6 +48,9 @@ const getHostQuery = graphql` ... on Instance { id host + localInstance { + slug + } } } } @@ -116,15 +122,21 @@ export default function CreateActorsPage(props: RouteSectionProps) { }); const submit: SubmitHandler = ({ size }) => { - const instance = data()?.instance?.id; + const { id: instance, host, localInstance } = data()?.instance ?? {}; + const slug = localInstance?.slug; - if (instance === undefined) { + if (instance === undefined || slug === undefined) { setErrorMessage("Instance not found."); return; } setErrorMessage(undefined); commitGenerateActors({ variables: { instance, size }, + updater: (store, response) => { + if (response?.generateActors.resultType === "CreateActorsSuccess") { + store.get(instance)?.invalidateRecord(); + } + }, onCompleted: (response, errors) => { const graphQLErrors = errors ?? []; if (graphQLErrors.length > 0) { @@ -137,7 +149,9 @@ export default function CreateActorsPage(props: RouteSectionProps) { const result = response.generateActors; switch (result.resultType) { case "CreateActorsSuccess": { - navigate(-1); + void revalidate("InstanceDetailQuery"); + navigate(`/instance/${encodeURIComponent(slug)}`); + showSuccessToast(`Successfully created actors on ${host}.`); return; } case "CreateActorsError": { @@ -197,7 +211,7 @@ export default function CreateActorsPage(props: RouteSectionProps) { @@ -229,7 +243,8 @@ export default function CreateActorsPage(props: RouteSectionProps) { class={styles.button} type="submit" disabled={ - isGeneratingActors() || data()?.instance?.id === undefined + isGeneratingActors() || + data()?.instance?.localInstance?.slug === undefined } > Create Actors diff --git a/packages/web/src/styles/toast.module.css b/packages/web/src/styles/toast.module.css new file mode 100644 index 0000000..cf1de48 --- /dev/null +++ b/packages/web/src/styles/toast.module.css @@ -0,0 +1,66 @@ +/* +DrFed: A web-based platform for developing and debugging ActivityPub apps +Copyright (C) 2026 DrFed team + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +.region { + position: fixed; + right: 1rem; + bottom: 1rem; + z-index: 100; + width: min(24rem, calc(100vw - 2rem)); +} + +.list { + display: flex; + flex-direction: column; + gap: 0.75rem; + margin: 0; + padding: 0; + list-style: none; +} + +.toast { + display: flex; + align-items: start; + gap: 1rem; + padding: 1rem; + background: var(--surface); + color: var(--ink); + border: 1px solid var(--line-strong); + border-left: 4px solid var(--success); + border-radius: var(--radius-sm); + box-shadow: var(--shadow-md); +} + +.title { + flex: 1; + font-weight: 600; + overflow-wrap: anywhere; +} + +.close { + border: 0; + background: transparent; + color: var(--ink-soft); + cursor: pointer; + font-size: 1.25rem; +} + +.close:focus-visible { + outline: 3px solid var(--accent-soft); + outline-offset: 0.2rem; +} From 661212073b5811f892bf07ecd32d139829e96d81 Mon Sep 17 00:00:00 2001 From: Jiwon Kwon Date: Tue, 15 Sep 2026 14:21:25 +0900 Subject: [PATCH 5/7] Add todo message --- .../web/src/routes/workspace/create/[instance_id]/actors.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx b/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx index 1677b9c..9e3e5c8 100644 --- a/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx +++ b/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx @@ -146,6 +146,7 @@ export default function CreateActorsPage(props: RouteSectionProps) { return; } + // TODO: Replace current codes to dictionary looking up const result = response.generateActors; switch (result.resultType) { case "CreateActorsSuccess": { From 6f9e4bbaf2fd04c386a34fc38f25b0d9d43c189e Mon Sep 17 00:00:00 2001 From: Jiwon Kwon Date: Tue, 15 Sep 2026 15:06:47 +0900 Subject: [PATCH 6/7] Reload instance details after actor creation Load the instance detail page afresh after creating actors and carry the success toast through session storage so it appears on the destination once. Normalize empty actor-count input to keep the size field editable. Codex generated these changes at my request after I reported stale instance details and an uneditable size field. I requested a full page refresh and verified that the resulting changes worked in the browser. Assisted-by: Codex:gpt-6-astra --- packages/web/src/components/Toast.tsx | 24 +++++++++++++++++++ .../workspace/create/[instance_id]/actors.tsx | 23 +++++++----------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/packages/web/src/components/Toast.tsx b/packages/web/src/components/Toast.tsx index dde293e..e27ee0e 100644 --- a/packages/web/src/components/Toast.tsx +++ b/packages/web/src/components/Toast.tsx @@ -15,11 +15,25 @@ // along with this program. If not, see . import { Toast, toaster } from "@kobalte/core/toast"; +import { onMount } from "solid-js"; import { Portal } from "solid-js/web"; import styles from "~/styles/toast.module.css"; +const pendingToastKey = "drfed:success-toast:"; + export function ToastRegion() { + onMount(() => { + const key = pendingToastKey + globalThis.location.pathname; + try { + const message = sessionStorage.getItem(key); + sessionStorage.removeItem(key); + if (message !== null && message !== "") showSuccessToast(message); + } catch { + // Navigation still works when browser storage is unavailable. + } + }); + return ( @@ -39,3 +53,13 @@ export function showSuccessToast(message: string): number { )); } + +/** Load a fresh page and show a one-time success notification there. */ +export function navigateWithSuccessToast(path: string, message: string): void { + try { + sessionStorage.setItem(pendingToastKey + path, message); + } catch { + // A blocked storage API must not prevent successful navigation. + } + globalThis.location.assign(path); +} diff --git a/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx b/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx index 9e3e5c8..e350372 100644 --- a/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx +++ b/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx @@ -22,8 +22,6 @@ import { type RouteDefinition, type RouteSectionProps, query, - revalidate, - useNavigate, } from "@solidjs/router"; import { graphql } from "relay-runtime"; import { Show, createSignal } from "solid-js"; @@ -35,7 +33,7 @@ import { } from "solid-relay"; import * as v from "valibot"; -import { showSuccessToast } from "~/components/Toast.tsx"; +import { navigateWithSuccessToast } from "~/components/Toast.tsx"; import type { GenerateActorsMutation } from "./__generated__/GenerateActorsMutation.graphql.ts"; import type { GetHostQuery } from "./__generated__/GetHostQuery.graphql.ts"; @@ -107,7 +105,6 @@ export default function CreateActorsPage(props: RouteSectionProps) { const [errorMessage, setErrorMessage] = createSignal(); const [commitGenerateActors, isGeneratingActors] = createMutation(generateActorsMutation); - const navigate = useNavigate(); const data = createPreloadedQuery( getHostQuery, @@ -132,11 +129,6 @@ export default function CreateActorsPage(props: RouteSectionProps) { setErrorMessage(undefined); commitGenerateActors({ variables: { instance, size }, - updater: (store, response) => { - if (response?.generateActors.resultType === "CreateActorsSuccess") { - store.get(instance)?.invalidateRecord(); - } - }, onCompleted: (response, errors) => { const graphQLErrors = errors ?? []; if (graphQLErrors.length > 0) { @@ -150,9 +142,10 @@ export default function CreateActorsPage(props: RouteSectionProps) { const result = response.generateActors; switch (result.resultType) { case "CreateActorsSuccess": { - void revalidate("InstanceDetailQuery"); - navigate(`/instance/${encodeURIComponent(slug)}`); - showSuccessToast(`Successfully created actors on ${host}.`); + navigateWithSuccessToast( + `/instance/${encodeURIComponent(slug)}`, + `Successfully created actors on ${host}.`, + ); return; } case "CreateActorsError": { @@ -212,9 +205,11 @@ export default function CreateActorsPage(props: RouteSectionProps) { { + field.onInput(Number.isNaN(value) ? undefined : value); + }} > Size Date: Tue, 15 Sep 2026 15:58:43 +0900 Subject: [PATCH 7/7] Support success and failure toast notifications Add status-specific toast colors and preserve the message and status across full-page navigation. Parse and validate saved notifications with a single Valibot pipeline, and pass success explicitly after actor creation. Codex updated the toast related helpers and the caller with success and failure varaints. I added Valibot and css. I verified the resulting changes in the browser. Assisted-by: Codex:gpt-6-astra --- packages/web/src/components/Toast.tsx | 41 +++++++++++++++---- .../workspace/create/[instance_id]/actors.tsx | 5 ++- packages/web/src/styles/toast.module.css | 9 +++- 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/packages/web/src/components/Toast.tsx b/packages/web/src/components/Toast.tsx index e27ee0e..2f6ddbf 100644 --- a/packages/web/src/components/Toast.tsx +++ b/packages/web/src/components/Toast.tsx @@ -17,20 +17,36 @@ import { Toast, toaster } from "@kobalte/core/toast"; import { onMount } from "solid-js"; import { Portal } from "solid-js/web"; +import * as v from "valibot"; import styles from "~/styles/toast.module.css"; -const pendingToastKey = "drfed:success-toast:"; +const pendingToastKey = "drfed:toast:"; + +const toastMessage = v.pipe(v.string(), v.nonEmpty()); +const storedToastSchema = v.pipe( + v.string(), + v.parseJson(), + v.object({ + message: toastMessage, + status: v.picklist(["success", "fail"]), + }), +); + +type ToastStatus = "success" | "fail"; export function ToastRegion() { onMount(() => { const key = pendingToastKey + globalThis.location.pathname; try { - const message = sessionStorage.getItem(key); + const stored = sessionStorage.getItem(key); sessionStorage.removeItem(key); - if (message !== null && message !== "") showSuccessToast(message); + const result = v.safeParse(storedToastSchema, stored); + if (result.success) { + showToast(result.output.message, result.output.status); + } } catch { - // Navigation still works when browser storage is unavailable. + // Browser storage is unavailable. } }); @@ -43,9 +59,9 @@ export function ToastRegion() { ); } -export function showSuccessToast(message: string): number { +export function showToast(message: string, status: ToastStatus): number { return toaster.show((props) => ( - + {message} @@ -54,10 +70,17 @@ export function showSuccessToast(message: string): number { )); } -/** Load a fresh page and show a one-time success notification there. */ -export function navigateWithSuccessToast(path: string, message: string): void { +/** Load a fresh page and show a one-time notification there. */ +export function navigateWithToast( + path: string, + message: string, + status: ToastStatus, +): void { try { - sessionStorage.setItem(pendingToastKey + path, message); + sessionStorage.setItem( + pendingToastKey + path, + JSON.stringify({ message, status }), + ); } catch { // A blocked storage API must not prevent successful navigation. } diff --git a/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx b/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx index e350372..31eef4c 100644 --- a/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx +++ b/packages/web/src/routes/workspace/create/[instance_id]/actors.tsx @@ -33,7 +33,7 @@ import { } from "solid-relay"; import * as v from "valibot"; -import { navigateWithSuccessToast } from "~/components/Toast.tsx"; +import { navigateWithToast } from "~/components/Toast.tsx"; import type { GenerateActorsMutation } from "./__generated__/GenerateActorsMutation.graphql.ts"; import type { GetHostQuery } from "./__generated__/GetHostQuery.graphql.ts"; @@ -142,9 +142,10 @@ export default function CreateActorsPage(props: RouteSectionProps) { const result = response.generateActors; switch (result.resultType) { case "CreateActorsSuccess": { - navigateWithSuccessToast( + navigateWithToast( `/instance/${encodeURIComponent(slug)}`, `Successfully created actors on ${host}.`, + "success", ); return; } diff --git a/packages/web/src/styles/toast.module.css b/packages/web/src/styles/toast.module.css index cf1de48..05ccf5b 100644 --- a/packages/web/src/styles/toast.module.css +++ b/packages/web/src/styles/toast.module.css @@ -41,11 +41,18 @@ along with this program. If not, see . background: var(--surface); color: var(--ink); border: 1px solid var(--line-strong); - border-left: 4px solid var(--success); border-radius: var(--radius-sm); box-shadow: var(--shadow-md); } +.toast.success { + border-left: 4px solid var(--success); +} + +.toast.fail { + border-left: 4px solid var(--danger); +} + .title { flex: 1; font-weight: 600;