Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
bfe3256
Use native Effect predicates in runtime and test assertions
BleedingDev Sep 7, 2026
bb7f291
Enforce native Effect interfaces and remove generator Promise bridges
BleedingDev Sep 7, 2026
0ca264e
Keep foreign SDK callbacks compatible with Effect diagnostics
BleedingDev Sep 7, 2026
e7aff4c
Close test-local Promise and imported tag assertion bypasses
BleedingDev Sep 7, 2026
f01183e
Merge remote-tracking branch 'origin/main' into codex/effect-native-e…
BleedingDev Sep 7, 2026
0b735d1
Respect tag exemptions in assertion comparisons
BleedingDev Sep 7, 2026
be56a69
Verify assertion imports and honor switch tag exemptions
BleedingDev Sep 7, 2026
536d650
Preserve tag checks through Rstest assertion exports
BleedingDev Sep 7, 2026
48c5d41
test(rstest): vendor @app/effect-rstest and run pilot suites through …
BleedingDev Sep 7, 2026
02ed274
Limit assertion tag detection to compared value projections
BleedingDev Sep 7, 2026
4d92f4f
Merge remote-tracking branch 'origin/main' into codex/effect-native-e…
BleedingDev Sep 7, 2026
095e1ae
Resolve Promise-returning generic function aliases at port declarations
BleedingDev Sep 7, 2026
e63db35
Merge remote-tracking branch 'origin/main' into codex/effect-native-e…
BleedingDev Sep 7, 2026
7e60d55
test(rstest): migrate every test to Rstest + @app/effect-rstest and d…
BleedingDev Sep 8, 2026
9786dd3
test(rstest): close enforcement gaps and verify scoped cleanup
BleedingDev Sep 8, 2026
eb555c8
Merge shared PR 501 regressions into Effect-native Rstest migration
BleedingDev Sep 8, 2026
fba58bc
test(auth): distinguish successful navigation from router failure
BleedingDev Sep 8, 2026
57b30d2
Merge current main governed HTTP regressions into native Rstest migra…
BleedingDev Sep 8, 2026
d8d9dc9
test(rstest): interrupt timed-out layers and verify generated checker…
BleedingDev Sep 8, 2026
3686c6b
Merge latest main nested fluent-slot regression fix
BleedingDev Sep 8, 2026
9e1ec76
test(rstest): support schema properties and Effect semantic equality
BleedingDev Sep 8, 2026
1a63811
test(ci): isolate analyzer colors and retain wire codec requirements
BleedingDev Sep 8, 2026
5db9cac
test(rstest): read subprocess reports independently of CLI banners
BleedingDev Sep 8, 2026
a16a02e
docs(testing): document local adapter corrections and property APIs
BleedingDev Sep 8, 2026
88781a5
test(lint): track native test APIs inside wrapped suites
BleedingDev Sep 8, 2026
e6d2b73
test(rstest): await timed-out test finalizers before suite release
BleedingDev Sep 8, 2026
a46844c
test(playwright): load Core through native Node TypeScript boundary
BleedingDev Sep 8, 2026
73def9f
test(effect): remove generic Promise adapter helpers
BleedingDev Sep 8, 2026
72f7f3f
test(effect): compose native programs and reject Promise round trips
BleedingDev Sep 8, 2026
13e692a
test(rstest): isolate generated validator execution
BleedingDev Sep 8, 2026
47186bd
test(effect): remove browser runtime and loader Promise round trips
BleedingDev Sep 8, 2026
87d10f5
test(rstest): remove forced Core serialization
BleedingDev Sep 8, 2026
2c7a151
fix(lint): recognize genuine Playwright extended test bindings
BleedingDev Sep 8, 2026
4bf1565
fix(lint): close destructured and partial tag assertion gaps
BleedingDev Sep 8, 2026
0b0dad1
test(e2e): enforce isolated parallel browser coverage in CI
BleedingDev Sep 8, 2026
1733f1b
test(effect): assert object variants through native schemas and predi…
BleedingDev Sep 8, 2026
50c3d45
test(integration): scope Party fixture mutations to owned records
BleedingDev Sep 8, 2026
bdeaf51
fix(lint): recognize native test hooks and property tag assertions
BleedingDev Sep 8, 2026
bc46ea0
test(effect-rstest): pin upstream table row semantics
BleedingDev Sep 8, 2026
a4a6eb5
fix(lint): inspect inherited and applied generic Promise ports
BleedingDev Sep 8, 2026
7aa2ab5
refactor(test): replace vendored runner with patched effect-rstest
BleedingDev Sep 8, 2026
9f960e3
merge: reconcile native Effect Rstest with cleanup guardrails
BleedingDev Sep 8, 2026
d0c4992
merge: preserve published upstream adapter adoption history
BleedingDev Sep 8, 2026
46c7bfa
merge: retain current main deployment and PostgreSQL updates
BleedingDev Sep 8, 2026
ddaa97c
fix(quality): model native Rstest project environments
BleedingDev Sep 8, 2026
06d58ee
fix(lint): inspect proven asymmetric tag assertions
BleedingDev Sep 8, 2026
dfe49e6
fix(lint): recognize negated asymmetric tag assertions
BleedingDev Sep 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 8 additions & 0 deletions .github/workflows/ultramodern-workspace-gates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,14 @@ jobs:
working-directory: app
run: mise exec -- pnpm test:integration

- name: Install Chromium and its runtime dependencies
working-directory: app
run: mise exec -- pnpm --filter @app/shell-super-app exec playwright install --with-deps chromium

- name: Run parallel authenticated browser tests without retries
working-directory: app
run: mise exec -- pnpm --filter @app/shell-super-app test:e2e

- name: Show bounded service diagnostics after failure
if: failure()
working-directory: app
Expand Down
27 changes: 19 additions & 8 deletions app/apps/shell-super-app/api/auth/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1053,18 +1053,29 @@ const assembleAuthenticationService = (
};
};

export const makeAuthenticationService = assembleAuthenticationService;
export interface AuthenticationServiceOptions {
readonly allowFixtureSignUp?: boolean;
}

export const AuthenticationServiceLive = Layer.effect(
AuthenticationService,
Effect.gen(function* makeAuthenticationServiceEffect() {
/**
* Builds the authentication service from AuthConfig, AuthDatabase, and PrincipalResolver in the
* caller's Effect context. Better Auth invokes its session hooks as Promises, so the resolver bridge
* is captured here and never leaves this module.
*/
export const makeAuthenticationService = Effect.fn('AuthenticationService.make')(
function* makeAuthenticationServiceEffect(options: AuthenticationServiceOptions = {}) {
const configuration = yield* AuthConfig;
const database = yield* AuthDatabase;
const resolver = yield* PrincipalResolver;
const effectContext = yield* Effect.context();
const runResolverEffect = Effect.runPromiseWith(effectContext);
return makeAuthenticationService(configuration, database.adapter, resolver, {
runResolverEffect,
return assembleAuthenticationService(configuration, database.adapter, resolver, {
...options,
runResolverEffect: Effect.runPromiseWith(effectContext),
});
}),
},
);

export const AuthenticationServiceLive = Layer.effect(
AuthenticationService,
makeAuthenticationService(),
);
13 changes: 4 additions & 9 deletions app/apps/shell-super-app/api/modules/installed-module-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ const unavailableErrorFields = {
reason: Schema.String,
};

const invokePromiseWithoutSignal =
<Value>(operation: () => PromiseLike<Value>) =>
(_signal: AbortSignal): PromiseLike<Value> =>
operation();
const InstalledModuleCatalogUnavailableErrorSchema = Schema.TaggedStruct(
'InstalledModuleCatalogUnavailableError',
unavailableErrorFields,
Expand Down Expand Up @@ -141,7 +137,8 @@ const readResponseChunks = (
> =>
Effect.tryPromise({
catch: unavailable,
try: invokePromiseWithoutSignal(reader.read.bind(reader)),
// oxlint-disable-next-line typescript/promise-function-async -- Effect owns this foreign stream Promise boundary.
try: () => reader.read(),
}).pipe(
Effect.timeout(timeout),
Effect.flatMap((next) => {
Expand Down Expand Up @@ -199,10 +196,8 @@ const readBoundedContract = Effect.fn('ShellInstalledModuleCatalog.readBoundedCo
const text = yield* Effect.acquireUseRelease(
Effect.succeed(reader),
(bodyReader) => collectResponseBody(bodyReader, maxBytes, timeout),
(bodyReader) =>
Effect.promise(
invokePromiseWithoutSignal(bodyReader.cancel.bind(bodyReader, undefined)),
).pipe(Effect.ignore),
// oxlint-disable-next-line typescript/promise-function-async -- Effect owns this foreign stream Promise boundary.
(bodyReader) => Effect.promise(() => bodyReader.cancel()).pipe(Effect.ignore),
);
return yield* decodeContractDocument(text).pipe(Effect.mapError((cause) => invalid(cause)));
},
Expand Down
9 changes: 5 additions & 4 deletions app/apps/shell-super-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
"serve": "modern serve",
"stage:bootstrap-demo": "sh scripts/bootstrap-stage-demo.sh",
"test:e2e": "playwright test",
"test:integration": "node --test tests/integration/*.test.ts",
"test:unit": "rstest",
"test:integration": "rstest --project integration",
"test:unit": "rstest --project unit",
"typecheck": "node ../../scripts/ultramodern-typecheck.mts --project tsconfig.json"
},
"dependencies": {
Expand Down Expand Up @@ -57,7 +57,7 @@
"@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.8.2-ultramodern.12",
"@playwright/test": "1.61.0",
"@rsbuild/plugin-tailwindcss": "^2.0.3",
"@rstest/core": "0.11.10",
"@rstest/core": "0.11.11",
"@testing-library/dom": "10.4.1",
"@testing-library/react": "16.3.2",
"@testing-library/user-event": "14.6.1",
Expand All @@ -71,7 +71,8 @@
"tailwindcss": "^4.3.2",
"typescript": "7.0.2",
"wrangler": "4.110.0",
"zephyr-rspack-plugin": "1.2.4"
"zephyr-rspack-plugin": "1.2.4",
"effect-rstest": "https://pkg.pr.new/ScriptedAlchemy/effect-rstest@79abbf684c7b150ee5f32694129a7caf969903bc"
},
"modernjs": {
"preset": "presetUltramodern",
Expand Down
12 changes: 9 additions & 3 deletions app/apps/shell-super-app/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/// <reference types="node" />
import { availableParallelism } from 'node:os';
import path from 'node:path';
import { APP_ENV_PATH } from '@app/core-runtime/workspace-environment';
import { defineConfig, devices } from '@playwright/test';
Expand Down Expand Up @@ -30,19 +32,22 @@ const repositoryRoot = path.resolve(process.cwd(), '../../..');
const e2eSourceRevision = '0000000000000000000000000000000000000001';

export default defineConfig({
// Preserve one native Core module instance and let Node strip its type-only class fields.
build: { external: ['**/packages/core-runtime/**'] },
forbidOnly: continuousIntegration,
fullyParallel: true,
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
reporter: 'line',
retries: continuousIntegration ? 2 : 0,
retries: 0,
testDir: './tests/e2e',
use: {
baseURL: origin,
trace: 'on-first-retry',
trace: 'retain-on-failure',
},
webServer: [
{
Expand All @@ -54,12 +59,13 @@ export default defineConfig({
ULTRAMODERN_SOURCE_REVISION: e2eSourceRevision,
},
reuseExistingServer: !continuousIntegration,
url: 'http://127.0.0.1:4102/en',
url: 'http://127.0.0.1:4102/party-registry-api/party-registry/readiness',
},
{
command: 'pnpm dev',
reuseExistingServer: !continuousIntegration,
url: `${origin}/en`,
},
],
workers: Math.max(1, availableParallelism() - 1),
});
75 changes: 59 additions & 16 deletions app/apps/shell-super-app/rstest.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { withModernConfig } from '@modern-js/adapter-rstest';
import { defineConfig } from '@rstest/core';
import type { Rspack } from '@rstest/core';
import { Result, Schema } from 'effect';

import {
Expand Down Expand Up @@ -52,22 +55,62 @@ const encodedSiteUrl = Result.getOrThrow(
Schema.encodeResult(siteUrlJsonSchema)('http://localhost:3020'),
);

const coreRuntimeRoot = fileURLToPath(new URL('../../packages/core-runtime/', import.meta.url));
// Generated owner modules are imported natively from disk, so core-runtime must be one Node
// instance shared by the test bundle and those modules (brand symbols, private fields).
const externalizeCoreRuntime = (
{ context, request }: Rspack.ExternalItemFunctionData,
resolveExternal: (error?: Error, external?: string) => void,
): void => {
if (request === undefined || context === undefined) {
resolveExternal();
return;
}
if (/^@app\/core-runtime(?:\/|$)/u.test(request)) {
resolveExternal(undefined, `module-import ${request}`);
return;
}
const resolved = request.startsWith('.') ? path.resolve(context, request) : undefined;
if (resolved !== undefined && resolved.startsWith(coreRuntimeRoot)) {
resolveExternal(undefined, `module-import ${pathToFileURL(resolved).href}`);
return;
}
resolveExternal();
};

// SWC rejects every generic arrow function in the imported `scripts/**/*.mts` files (even
// `<T,>(...)`) under its default mts/cts parser mode. The parser key is missing from the
// bundled swc types, so the object is declared here instead of inline.
const swc = { jsc: { parser: { disallowAmbiguousJsxLike: false, syntax: 'typescript' } } } as const;

export default defineConfig({
clearMocks: true,
extends: withModernConfig({
configPath: './modern.rstest.config.ts',
}),
include: ['tests/unit/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
output: {
module: false,
},
restoreMocks: true,
source: {
define: {
ULTRAMODERN_GATEWAY_AUDIENCE_TOPOLOGY: encodedReferenceTopology,
ULTRAMODERN_MODULE_DEPLOYMENT_ALLOWLIST: encodedModuleDeploymentAllowlist,
ULTRAMODERN_SITE_URL: encodedSiteUrl,
projects: [
{
clearMocks: true,
extends: withModernConfig({
configPath: './modern.rstest.config.ts',
}),
include: ['tests/unit/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
name: 'unit',
output: {
module: false,
},
restoreMocks: true,
source: {
define: {
ULTRAMODERN_GATEWAY_AUDIENCE_TOPOLOGY: encodedReferenceTopology,
ULTRAMODERN_MODULE_DEPLOYMENT_ALLOWLIST: encodedModuleDeploymentAllowlist,
ULTRAMODERN_SITE_URL: encodedSiteUrl,
},
},
testEnvironment: 'happy-dom',
Comment thread
BleedingDev marked this conversation as resolved.
},
{
include: ['tests/integration/**/*.test.ts'],
name: 'integration',
testEnvironment: 'node',
testTimeout: 30_000,
tools: { rspack: { externals: [externalizeCoreRuntime] }, swc },
},
},
testEnvironment: 'happy-dom',
],
});
4 changes: 2 additions & 2 deletions app/apps/shell-super-app/src/routes/[lang]/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Toaster, useToast } from '@techsio/ui-kit/molecules/toast';
import { useRef, useState } from 'react';
import { signIn } from '../../../api/auth-client.ts';
import type { ShellAuthenticationClientError } from '../../../api/auth-client.ts';
import { runBrowserEffect } from '../../../runtime/browser-effect-runtime.ts';
import { browserRuntime } from '../../../runtime/browser-effect-runtime.ts';
import { SignInPayloadSchema } from '../../../../shared/api.ts';
import { UltramodernRouteHead } from '../../ultramodern-route-head';

Expand Down Expand Up @@ -109,7 +109,7 @@ const LoginPage = () => {
}

setSubmitting(true);
void runBrowserEffect(
void browserRuntime.runPromise(
signIn(credentials.value, { locale: language }).pipe(
Effect.matchEffect({
onFailure: (error) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Effect, Match, Predicate, Schema } from 'effect';
import type { Config } from 'effect';
import type { Cause, Config } from 'effect';
import { ResolveModuleTargetPayloadSchema } from '../../../../../shared/api.ts';
import type { ResolvedModuleTarget } from '../../../../../shared/api.ts';
import { resolveModuleTarget } from '../../../../api/auth-client.ts';
import type { ShellTargetClientError } from '../../../../api/auth-client.ts';
import { runBrowserEffect } from '../../../../runtime/browser-effect-runtime.ts';
import { browserRuntime } from '../../../../runtime/browser-effect-runtime.ts';
import { shellAuthenticationClientOptionsFromRequest } from '../../../shell-authentication-client-options.ts';
import { loadHomePageModel } from '../../page.data.ts';
import type { HomePageModel } from '../../page.data.ts';
Expand Down Expand Up @@ -96,45 +96,46 @@ const safeState = (
Match.exhaustive,
);

export const loader = ({
export const loadModulePageModel = ({
params,
request,
routeParams = {},
}: ModuleTargetLoaderArguments): Promise<ModuleTargetPageModel> =>
runBrowserEffect(
Effect.tryPromise(() => loadHomePageModel(request)).pipe(
Effect.timeout('30 seconds'),
Effect.flatMap((shell) => {
if (shell.state !== 'authenticated') {
return Effect.succeed<ModuleTargetPageModel>({
shell,
state: shell.state === 'unavailable' ? 'unavailable' : 'selection_required',
});
}
const boundedRouteParams = selectRouteParams(routeParams, Object.keys(routeParams));
return shellAuthenticationClientOptionsFromRequest(request).pipe(
Effect.flatMap((options) =>
Schema.decodeUnknownEffect(ResolveModuleTargetPayloadSchema)(
withOptionalProperty(
{},
params.entrypointKey !== undefined,
'entrypointKey',
params.entrypointKey,
{ moduleId: params.moduleId },
),
).pipe(Effect.flatMap((payload) => resolveModuleTarget(payload, options))),
),
Effect.map((target): ModuleTargetPageModel => ({
routeParams: boundedRouteParams,
shell,
state: 'resolved',
target,
})),
Effect.matchEffect({
onFailure: (error) => Effect.succeed(safeState(error, shell)),
onSuccess: Effect.succeed,
}),
);
}),
),
}: ModuleTargetLoaderArguments): Effect.Effect<ModuleTargetPageModel, Cause.TimeoutError> =>
loadHomePageModel(request).pipe(
Effect.timeout('30 seconds'),
Effect.flatMap((shell) => {
if (shell.state !== 'authenticated') {
return Effect.succeed<ModuleTargetPageModel>({
shell,
state: shell.state === 'unavailable' ? 'unavailable' : 'selection_required',
});
}
const boundedRouteParams = selectRouteParams(routeParams, Object.keys(routeParams));
return shellAuthenticationClientOptionsFromRequest(request).pipe(
Effect.flatMap((options) =>
Schema.decodeUnknownEffect(ResolveModuleTargetPayloadSchema)(
withOptionalProperty(
{},
params.entrypointKey !== undefined,
'entrypointKey',
params.entrypointKey,
{ moduleId: params.moduleId },
),
).pipe(Effect.flatMap((payload) => resolveModuleTarget(payload, options))),
),
Effect.map((target): ModuleTargetPageModel => ({
routeParams: boundedRouteParams,
shell,
state: 'resolved',
target,
})),
Effect.matchEffect({
onFailure: (error) => Effect.succeed(safeState(error, shell)),
onSuccess: Effect.succeed,
}),
);
}),
);

export const loader = (input: ModuleTargetLoaderArguments): Promise<ModuleTargetPageModel> =>
browserRuntime.runPromise(loadModulePageModel(input), { signal: input.request.signal });
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Effect, Predicate } from 'effect';
import { useEffect, useState } from 'react';
import type { ApprovedVerticalPageComponent } from '../../../../api/vertical-clients.ts';
import { findApprovedVerticalPageClient } from '../../../../api/vertical-clients.ts';
import { runBrowserEffect } from '../../../../runtime/browser-effect-runtime.ts';
import { browserRuntime } from '../../../../runtime/browser-effect-runtime.ts';
import {
resolveThenLoadModuleTarget,
settleModuleEntrypointLoad,
Expand Down Expand Up @@ -38,7 +38,7 @@ const ResolvedTarget = ({
return;
}
let current = true;
void runBrowserEffect(
void browserRuntime.runPromise(
resolveThenLoadModuleTarget(Effect.succeed(model.target), () =>
settleModuleEntrypointLoad(
client.load,
Expand Down
Loading
Loading