View Tag types
A View Tag is a class — same shape as Effect’s Context.Service. Mint with View.Card.Tag (etc.), write the skin, export layer.
One-shot mint (common path)
View.Card / Detail / Page are size chrome already fulfilled (size: ViewKind.Card() …). Stamp spec, optional extra props, mint:
import { import LayerLayer } from "effect"
import { import ViewView } from "hyperlink-ts/ui"
class class PoolCardPoolCard extends import ViewView.const Card: View.FulfilledPrototype<
View.ViewProps,
View.WithSize<{
readonly _tag: "Card"
}>
>
Size-chrome add-ons —
SizeChrome
with size fulfilled.
Mint with View.Card.Tag<Self, Props?>()(key, statics?).
Matcher components are not these — use View.react(…).Card or
useMatch
.
Card.Prototype<ViewProps, {}, WithSize<{ readonly _tag: "Card"; }>>.Tag: <PoolCard, {}>() => <K, NewStatics>(key: K, statics?: NewStatics | undefined) => ServiceClass<PoolCard, K, View.View<{
readonly tag: View.ViewTag;
readonly name?: string | undefined;
}>> & { readonly [K in keyof (View.WithSize<{
readonly _tag: "Card";
}> & NewStatics)]: (View.WithSize<{
readonly _tag: "Card";
}> & NewStatics)[K]; } & {
readonly Type: {
readonly tag: View.ViewTag;
readonly name?: string | undefined;
};
}
Mint a Context.Service class handle (Effect Service<Self, View<Props>>()("key")).
NewProps — extra component props (additive)
statics — optional runtime statics (spec, …); merged onto the class
- Annotate skins with
PoolCard["Service"] (no typeof)
Does not change this Prototype’s Requirement type — returns a class.
For bind, the class still needs .size (use
Card
/
Detail
/
Page
, or pass size in statics / fulfill via .Prototype() first).
Tag<class PoolCardPoolCard>()(
"app/view/pool-card",
{ spec: {
readonly kind: "app/queue"
}
spec: { kind: "app/queue"kind: "app/queue" } as type const = {
readonly kind: "app/queue";
}
const },
) {}
class class PoolDetailPoolDetail extends import ViewView.const Detail: View.FulfilledPrototype<
View.ViewProps,
View.WithSize<{
readonly _tag: "Detail"
}>
>
Detail.Prototype<ViewProps, {}, WithSize<{ readonly _tag: "Detail"; }>>.Tag: <PoolDetail, {}>() => <K, NewStatics>(key: K, statics?: NewStatics | undefined) => ServiceClass<PoolDetail, K, View.View<{
readonly tag: View.ViewTag;
readonly name?: string | undefined;
}>> & { readonly [K in keyof (View.WithSize<{
readonly _tag: "Detail";
}> & NewStatics)]: (View.WithSize<{
readonly _tag: "Detail";
}> & NewStatics)[K]; } & {
readonly Type: {
readonly tag: View.ViewTag;
readonly name?: string | undefined;
};
}
Mint a Context.Service class handle (Effect Service<Self, View<Props>>()("key")).
NewProps — extra component props (additive)
statics — optional runtime statics (spec, …); merged onto the class
- Annotate skins with
PoolCard["Service"] (no typeof)
Does not change this Prototype’s Requirement type — returns a class.
For bind, the class still needs .size (use
Card
/
Detail
/
Page
, or pass size in statics / fulfill via .Prototype() first).
Tag<class PoolDetailPoolDetail>()(
"app/view/pool-detail",
{ spec: {
readonly kind: "app/queue"
}
spec: { kind: "app/queue"kind: "app/queue" } as type const = {
readonly kind: "app/queue";
}
const },
) {}
class class PoolPagePoolPage extends import ViewView.const Page: View.FulfilledPrototype<
View.ViewProps,
View.WithSize<{
readonly _tag: "Page"
}>
>
Page.Prototype<ViewProps, {}, WithSize<{ readonly _tag: "Page"; }>>.Tag: <PoolPage, {}>() => <K, NewStatics>(key: K, statics?: NewStatics | undefined) => ServiceClass<PoolPage, K, View.View<{
readonly tag: View.ViewTag;
readonly name?: string | undefined;
}>> & { readonly [K in keyof (View.WithSize<{
readonly _tag: "Page";
}> & NewStatics)]: (View.WithSize<{
readonly _tag: "Page";
}> & NewStatics)[K]; } & {
readonly Type: {
readonly tag: View.ViewTag;
readonly name?: string | undefined;
};
}
Mint a Context.Service class handle (Effect Service<Self, View<Props>>()("key")).
NewProps — extra component props (additive)
statics — optional runtime statics (spec, …); merged onto the class
- Annotate skins with
PoolCard["Service"] (no typeof)
Does not change this Prototype’s Requirement type — returns a class.
For bind, the class still needs .size (use
Card
/
Detail
/
Page
, or pass size in statics / fulfill via .Prototype() first).
Tag<class PoolPagePoolPage>()(
"app/view/pool-page",
{ spec: {
readonly kind: "app/queue"
}
spec: { kind: "app/queue"kind: "app/queue" } as type const = {
readonly kind: "app/queue";
}
const },
) {}
class PoolCardPoolCard.size: {
readonly _tag: "Card"
}
size
class PoolCardPoolCard.size: {
readonly _tag: "Card"
}
size._tag: "Card"_tag
const const PoolCardView: View.View<{
readonly tag: View.ViewTag
readonly name?: string | undefined
}>
PoolCardView: class PoolCardPoolCard["Service"] = (props: {
readonly tag: View.ViewTag
readonly name?: string | undefined
}
props) => {
props: {
readonly tag: View.ViewTag
readonly name?: string | undefined
}
props
return null
}
const const PoolDetailView: View.View<{
readonly tag: View.ViewTag
readonly name?: string | undefined
}>
PoolDetailView: class PoolDetailPoolDetail["Service"] = (_props: {
readonly tag: View.ViewTag
readonly name?: string | undefined
}
_props) => null
const const PoolPageView: View.View<{
readonly tag: View.ViewTag
readonly name?: string | undefined
}>
PoolPageView: class PoolPagePoolPage["Service"] = (_props: {
readonly tag: View.ViewTag
readonly name?: string | undefined
}
_props) => null
export const const layer: Layer.Layer<
unknown,
never,
never
>
const layer: {
build: (memoMap: MemoMap, scope: Scope.Scope) => Effect<Context.Context<unknown>, never, never>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
layer = import LayerLayer.const mergeAll: <
Layers extends [
Layer<never, any, any>,
...Array<Layer<never, any, any>>
]
>(
...layers: Layers
) => Layer<
Success<Layers[number]>,
Error<Layers[number]>,
Services<Layers[number]>
>
Combines all the provided layers concurrently, creating a new layer with
merged input, error, and output types.
When to use
Use when you need to combine multiple independent layers.
Details
All layers are built concurrently, and their outputs are merged into a single layer.
If multiple merged layers depend on the same layer value, that dependency is
shared by default. Reuse a named layer value when you want services to share
the same resource, such as one database pool.
Example (Merging independent layers)
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
class Logger extends Context.Service<Logger, {
readonly log: (msg: string) => Effect.Effect<void>
}>()("Logger") {}
const dbLayer = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result"))
})
const loggerLayer = Layer.succeed(Logger, {
log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => console.log(msg)))
})
const mergedLayer = Layer.mergeAll(dbLayer, loggerLayer)
mergeAll(
import LayerLayer.const succeed: {
<I, S>(service: Context.Key<I, S>): (
resource: S
) => Layer<I>
<I, S>(
service: Context.Key<I, S>,
resource: Types.NoInfer<S>
): Layer<I>
}
Constructs a layer that provides a single service from an already available
value.
When to use
Use when you need a Layer that provides a service from an already
constructed implementation without effectful acquisition.
Example (Creating a layer from a service implementation)
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
const DatabaseLive = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Query result: ${sql}`))
})
succeed(class PoolCardPoolCard, const PoolCardView: View.View<{
readonly tag: View.ViewTag
readonly name?: string | undefined
}>
PoolCardView),
import LayerLayer.const succeed: {
<I, S>(service: Context.Key<I, S>): (
resource: S
) => Layer<I>
<I, S>(
service: Context.Key<I, S>,
resource: Types.NoInfer<S>
): Layer<I>
}
Constructs a layer that provides a single service from an already available
value.
When to use
Use when you need a Layer that provides a service from an already
constructed implementation without effectful acquisition.
Example (Creating a layer from a service implementation)
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
const DatabaseLive = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Query result: ${sql}`))
})
succeed(class PoolDetailPoolDetail, const PoolDetailView: View.View<{
readonly tag: View.ViewTag
readonly name?: string | undefined
}>
PoolDetailView),
import LayerLayer.const succeed: {
<I, S>(service: Context.Key<I, S>): (
resource: S
) => Layer<I>
<I, S>(
service: Context.Key<I, S>,
resource: Types.NoInfer<S>
): Layer<I>
}
Constructs a layer that provides a single service from an already available
value.
When to use
Use when you need a Layer that provides a service from an already
constructed implementation without effectful acquisition.
Example (Creating a layer from a service implementation)
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
const DatabaseLive = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Query result: ${sql}`))
})
succeed(class PoolPagePoolPage, const PoolPageView: View.View<{
readonly tag: View.ViewTag
readonly name?: string | undefined
}>
PoolPageView),
)
const const kind:
| {
readonly _tag: "Card"
}
| {
readonly _tag: "Detail"
}
| {
readonly _tag: "Page"
}
kind: import ViewView.type ViewKind =
| {
readonly _tag: "Card"
}
| {
readonly _tag: "Detail"
}
| {
readonly _tag: "Page"
}
Building-block sizes (W8 / lock F1) as Effect tagged variants.
Content fills these — not separate kinds named after content.
Construct with
ViewKind
.Card() / .Detail() / .Page(); match with
Match.tag (same _tag style as
WidgetEntry
).
Constructors / $is / $match for
ViewKind
.
ViewKind = import ViewView.const ViewKind: {
readonly Card: TaggedEnum.ConstructorFrom<{
readonly _tag: "Card";
}, "_tag">;
readonly Detail: TaggedEnum.ConstructorFrom<{
readonly _tag: "Detail";
}, "_tag">;
readonly Page: TaggedEnum.ConstructorFrom<{
readonly _tag: "Page";
}, "_tag">;
readonly $is: <Tag extends "Card" | "Detail" | "Page">(tag: Tag) => (u: unknown) => u is Extract<{
readonly _tag: "Card";
}, {
readonly _tag: Tag;
}> | Extract<{
readonly _tag: "Detail";
}, {
readonly _tag: Tag;
}> | Extract<{
readonly _tag: "Page";
}, {
readonly _tag: Tag;
}>;
readonly $match: {
...;
};
}
Building-block sizes (W8 / lock F1) as Effect tagged variants.
Content fills these — not separate kinds named after content.
Construct with
ViewKind
.Card() / .Detail() / .Page(); match with
Match.tag (same _tag style as
WidgetEntry
).
Constructors / $is / $match for
ViewKind
.
ViewKind.type Card: (args: void) => {
readonly _tag: "Card"
}
Card()
const const label: stringlabel = import ViewView.const ViewKind: {
readonly Card: TaggedEnum.ConstructorFrom<{
readonly _tag: "Card";
}, "_tag">;
readonly Detail: TaggedEnum.ConstructorFrom<{
readonly _tag: "Detail";
}, "_tag">;
readonly Page: TaggedEnum.ConstructorFrom<{
readonly _tag: "Page";
}, "_tag">;
readonly $is: <Tag extends "Card" | "Detail" | "Page">(tag: Tag) => (u: unknown) => u is Extract<{
readonly _tag: "Card";
}, {
readonly _tag: Tag;
}> | Extract<{
readonly _tag: "Detail";
}, {
readonly _tag: Tag;
}> | Extract<{
readonly _tag: "Page";
}, {
readonly _tag: Tag;
}>;
readonly $match: {
...;
};
}
Building-block sizes (W8 / lock F1) as Effect tagged variants.
Content fills these — not separate kinds named after content.
Construct with
ViewKind
.Card() / .Detail() / .Page(); match with
Match.tag (same _tag style as
WidgetEntry
).
Constructors / $is / $match for
ViewKind
.
ViewKind.$match: <{
Card: () => string;
Detail: () => string;
Page: () => string;
}>(value: {
readonly _tag: "Card";
} | {
readonly _tag: "Detail";
} | {
readonly _tag: "Page";
}, cases: {
Card: () => string;
Detail: () => string;
Page: () => string;
}) => string (+1 overload)
$match(const kind: {
readonly _tag: "Card"
}
kind, {
type Card: () => stringCard: () => "card chrome",
type Detail: () => stringDetail: () => "detail chrome",
type Page: () => stringPage: () => "page chrome",
})
void const label: stringlabelAnnotate skins with PoolCard["Service"] (no typeof). Sizes are Data.TaggedEnum — match with ViewKind.$match (or Match.tag on a View.ViewKind-typed value).
Extra props
Second type arg on Tag — additive props; statics as the value arg:
import { import LayerLayer } from "effect"
import { import ViewView } from "hyperlink-ts/ui"
class class DenseCardDenseCard extends import ViewView.const Card: View.FulfilledPrototype<
View.ViewProps,
View.WithSize<{
readonly _tag: "Card"
}>
>
Size-chrome add-ons —
SizeChrome
with size fulfilled.
Mint with View.Card.Tag<Self, Props?>()(key, statics?).
Matcher components are not these — use View.react(…).Card or
useMatch
.
Card.Prototype<ViewProps, {}, WithSize<{ readonly _tag: "Card"; }>>.Tag: <DenseCard, {
readonly dense?: boolean;
}>() => <K, NewStatics>(key: K, statics?: NewStatics | undefined) => ServiceClass<DenseCard, K, View.View<{
readonly tag: View.ViewTag;
readonly name?: string | undefined;
readonly dense?: boolean | undefined;
}>> & { readonly [K in keyof (View.WithSize<{
readonly _tag: "Card";
}> & NewStatics)]: (View.WithSize<{
readonly _tag: "Card";
}> & NewStatics)[K]; } & {
readonly Type: {
readonly tag: View.ViewTag;
readonly name?: string | undefined;
readonly dense?: boolean | undefined;
};
}
Mint a Context.Service class handle (Effect Service<Self, View<Props>>()("key")).
NewProps — extra component props (additive)
statics — optional runtime statics (spec, …); merged onto the class
- Annotate skins with
PoolCard["Service"] (no typeof)
Does not change this Prototype’s Requirement type — returns a class.
For bind, the class still needs .size (use
Card
/
Detail
/
Page
, or pass size in statics / fulfill via .Prototype() first).
Tag<
class DenseCardDenseCard,
{ readonly dense?: boolean | undefineddense?: boolean }
>()("app/view/dense-card", {
spec: {
readonly kind: "app/dense-card"
}
spec: { kind: "app/dense-card"kind: "app/dense-card" } as type const = {
readonly kind: "app/dense-card";
}
const,
}) {}
const const DenseCardView: View.View<{
readonly tag: View.ViewTag
readonly name?: string | undefined
readonly dense?: boolean | undefined
}>
DenseCardView: class DenseCardDenseCard["Service"] = (props: {
readonly tag: View.ViewTag
readonly name?: string | undefined
readonly dense?: boolean | undefined
}
props) => {
props: {
readonly tag: View.ViewTag
readonly name?: string | undefined
readonly dense?: boolean | undefined
}
props
return null
}
export const const layer: Layer.Layer<
unknown,
never,
never
>
const layer: {
build: (memoMap: MemoMap, scope: Scope.Scope) => Effect<Context.Context<unknown>, never, never>;
pipe: { <A>(this: A): A; <A, B = never>(this: A, ab: (_: A) => B): B; <A, B = never, C = never>(this: A, ab: (_: A) => B, bc: (_: B) => C): C; <A, B = never, C = never, D = never>(this: A, ab: (_: A) => B, bc: (_: B) => C, cd: (_: C) => D): D; <…;
}
layer = import LayerLayer.const succeed: {
<I, S>(service: Context.Key<I, S>): (
resource: S
) => Layer<I>
<I, S>(
service: Context.Key<I, S>,
resource: Types.NoInfer<S>
): Layer<I>
}
Constructs a layer that provides a single service from an already available
value.
When to use
Use when you need a Layer that provides a service from an already
constructed implementation without effectful acquisition.
Example (Creating a layer from a service implementation)
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
const DatabaseLive = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Query result: ${sql}`))
})
succeed(class DenseCardDenseCard, const DenseCardView: View.View<{
readonly tag: View.ViewTag
readonly name?: string | undefined
readonly dense?: boolean | undefined
}>
DenseCardView)Naked (no size): View.Tag<Greeter, { name: string }>()("…").
Requirement (open chain)
When you need to declare debt before fulfilling — SizeChrome + .Prototype():
import { import ViewView } from "hyperlink-ts/ui"
const const Mid: View.Prototype<
{
readonly tag: View.ViewTag
readonly name?: string | undefined
readonly dense?: boolean | undefined
},
View.WithSize<
| {
readonly _tag: "Card"
}
| {
readonly _tag: "Detail"
}
| {
readonly _tag: "Page"
}
>,
{
readonly spec: {
readonly kind: "app/queue"
}
}
>
Mid = import ViewView.const SizeChrome: View.OpenPrototype<
View.ViewProps,
View.WithSize<
| {
readonly _tag: "Card"
}
| {
readonly _tag: "Detail"
}
| {
readonly _tag: "Page"
}
>
>
Shared size-chrome shell —
WithSize
Requirement open (not fulfilled).
Chain .Prototype() for props/statics, then fulfill with { size: ViewKind.Card() }.
(Named SizeChrome — layout hints stay
Chrome
.)
SizeChrome.Prototype<ViewProps, WithSize<{ readonly _tag: "Card"; } | { readonly _tag: "Detail"; } | { readonly _tag: "Page"; }>, {}>.Prototype: <{
readonly dense?: boolean;
}>() => <NewStatics>(statics?: NewStatics | undefined) => View.Prototype<{
readonly tag: View.ViewTag;
readonly name?: string | undefined;
readonly dense?: boolean | undefined;
}, NextRequirement<View.WithSize<{
readonly _tag: "Card";
} | {
readonly _tag: "Detail";
} | {
readonly _tag: "Page";
}>, { readonly [K in keyof ({} & NewStatics)]: ({} & NewStatics)[K]; }>, { readonly [K in keyof ({} & NewStatics)]: ({} & NewStatics)[K]; }>
Extend props (type arg) and/or statics (value). Both additive.
Requirement discharges when merged statics satisfy it.
Prototype<{ readonly dense?: boolean | undefineddense?: boolean }>()({
spec: {
readonly kind: "app/queue"
}
spec: { kind: "app/queue"kind: "app/queue" } as type const = {
readonly kind: "app/queue";
}
const,
})
// WithSize still open — fulfill last
const const Proto: View.Prototype<
{
readonly tag: View.ViewTag
readonly name?: string | undefined
readonly dense?: boolean | undefined
},
{},
{
readonly spec: {
readonly kind: "app/queue"
}
readonly size: {
readonly _tag: "Card"
}
}
>
Proto = const Mid: View.Prototype<
{
readonly tag: View.ViewTag
readonly name?: string | undefined
readonly dense?: boolean | undefined
},
View.WithSize<
| {
readonly _tag: "Card"
}
| {
readonly _tag: "Detail"
}
| {
readonly _tag: "Page"
}
>,
{
readonly spec: {
readonly kind: "app/queue"
}
}
>
Mid.Prototype<{ readonly tag: ViewTag; readonly name?: string | undefined; readonly dense?: boolean | undefined; }, WithSize<{ readonly _tag: "Card"; } | { readonly _tag: "Detail"; } | { readonly _tag: "Page"; }>, { ...; }>.Prototype: <{}>() => <NewStatics>(statics?: NewStatics | undefined) => View.Prototype<{
readonly tag: View.ViewTag;
readonly name?: string | undefined;
readonly dense?: boolean | undefined;
}, NextRequirement<View.WithSize<{
readonly _tag: "Card";
} | {
readonly _tag: "Detail";
} | {
readonly _tag: "Page";
}>, { readonly [K in keyof ({
readonly spec: {
readonly kind: "app/queue";
};
} & NewStatics)]: ({
readonly spec: {
readonly kind: "app/queue";
};
} & NewStatics)[K]; }>, { readonly [K in keyof ({
readonly spec: {
readonly kind: "app/queue";
};
} & NewStatics)]: ({
readonly spec: {
readonly kind: "app/queue";
};
} & NewStatics)[K]; }>
Extend props (type arg) and/or statics (value). Both additive.
Requirement discharges when merged statics satisfy it.
Prototype()({ size: {
readonly _tag: "Card"
}
size: import ViewView.const ViewKind: {
readonly Card: TaggedEnum.ConstructorFrom<{
readonly _tag: "Card";
}, "_tag">;
readonly Detail: TaggedEnum.ConstructorFrom<{
readonly _tag: "Detail";
}, "_tag">;
readonly Page: TaggedEnum.ConstructorFrom<{
readonly _tag: "Page";
}, "_tag">;
readonly $is: <Tag extends "Card" | "Detail" | "Page">(tag: Tag) => (u: unknown) => u is Extract<{
readonly _tag: "Card";
}, {
readonly _tag: Tag;
}> | Extract<{
readonly _tag: "Detail";
}, {
readonly _tag: Tag;
}> | Extract<{
readonly _tag: "Page";
}, {
readonly _tag: Tag;
}>;
readonly $match: {
...;
};
}
Building-block sizes (W8 / lock F1) as Effect tagged variants.
Content fills these — not separate kinds named after content.
Construct with
ViewKind
.Card() / .Detail() / .Page(); match with
Match.tag (same _tag style as
WidgetEntry
).
Constructors / $is / $match for
ViewKind
.
ViewKind.type Card: (args: void) => {
readonly _tag: "Card"
}
Card() })
class class PoolCardPoolCard extends const Proto: View.Prototype<
{
readonly tag: View.ViewTag
readonly name?: string | undefined
readonly dense?: boolean | undefined
},
{},
{
readonly spec: {
readonly kind: "app/queue"
}
readonly size: {
readonly _tag: "Card"
}
}
>
Proto.Prototype<{ readonly tag: ViewTag; readonly name?: string | undefined; readonly dense?: boolean | undefined; }, {}, { readonly spec: { readonly kind: "app/queue"; }; readonly size: { readonly _tag: "Card"; }; }>.Tag: <PoolCard, {}>() => <K, NewStatics>(key: K, statics?: NewStatics | undefined) => ServiceClass<PoolCard, K, View.View<{
readonly tag: View.ViewTag;
readonly name?: string | undefined;
readonly dense?: boolean | undefined;
}>> & { readonly [K in keyof ({
readonly spec: {
readonly kind: "app/queue";
};
readonly size: {
readonly _tag: "Card";
};
} & NewStatics)]: ({
readonly spec: {
readonly kind: "app/queue";
};
readonly size: {
readonly _tag: "Card";
};
} & NewStatics)[K]; } & {
readonly Type: {
readonly tag: View.ViewTag;
readonly name?: string | undefined;
readonly dense?: boolean | undefined;
};
}
Mint a Context.Service class handle (Effect Service<Self, View<Props>>()("key")).
NewProps — extra component props (additive)
statics — optional runtime statics (spec, …); merged onto the class
- Annotate skins with
PoolCard["Service"] (no typeof)
Does not change this Prototype’s Requirement type — returns a class.
For bind, the class still needs .size (use
Card
/
Detail
/
Page
, or pass size in statics / fulfill via .Prototype() first).
Tag<class PoolCardPoolCard>()("app/view/pool-card") {}
class PoolCardPoolCard.size: {
readonly _tag: "Card"
}
size
Wire into Dashboard
import { Layer } from "effect"
import { View } from "hyperlink-ts/ui"
import { WorkerPool } from "./hub"
export class WorkerPoolCard extends View.Card.Tag<
WorkerPoolCard,
{ readonly dense?: boolean }
>()("examples/hyperlink-web/worker-pool-card", {
spec: { kind: "examples/worker-pool-card" } as const,
}) {}
const WorkerPoolCardView: WorkerPoolCard["Service"] = (_props) => null
export const layer = View.only(WorkerPool, WorkerPoolCard).pipe(
Layer.provide(Layer.succeed(WorkerPoolCard, WorkerPoolCardView)),
)Full dogfood: examples/hyperlink-web/worker-pool-card.tsx.