npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cratis/scene

v0.157.3

Published

Cratis Scene: renders Studio prototypes as thumbnails and previews.

Downloads

17,578

Readme

Scene

TypeScript models for the Studio's low-code canvas: UIElement hierarchy, ExternalComponent, and the prototype surface component system.

Serialized prototype previews

PrototypeThumbnail accepts both live IComponent instances and raw UIElement data from JSON documents. At render time Scene uses the element's library-qualified _derivedTypeId (or componentName) to construct a registered component and copy its saved state, recursively hydrating element arrays in all named slots without changing the source document. Without a registered library, it renders a positioned, sized placeholder rather than an empty prototype. Library registration does not trigger a React rerender by itself; rerender the thumbnail after loading bundles to replace placeholders with actual components. Without an open action the thumbnail is a named, non-focusable preview; with one it remains an editor-opening button. The board/document reader does not load bundles or depend on Studio Core.

Layout preview DTOs

The six StudioLayout.* types (Stack, Row, Grid, Container, Wrap, and Canvas) are backend preview DTOs. They derive from the existing FrameworkElement/UIElement hierarchy and use the same on-wire ComponentName, inherited Properties, and Slots shape as ExternalComponent, so generic board previews can round-trip their nested children. They deliberately do not derive from ExternalComponent: the current derived-type converter treats a base with registered subclasses as a discriminator family and then cannot deserialize a generic externalComponent itself. This compatibility boundary avoids changing Fundamentals or breaking existing generic component, Dialog, and ConfirmDialog data.

A SceneDocument is the sole editable model. Layout DTOs are produced only for the legacy board-preview projection; they are not a second Scene model, editor, resolver, or persistence authority.

Component class — how it works

Component (in Component.ts) is the abstract base class for every prototype surface component (e.g. the 89 PrimeReact wrappers under Source/PrimeReact/). It extends ExternalComponent so instances are proper UIElement subtypes that flow through the command pipeline without any manual wire-format conversion.

Why extends ExternalComponent?

Commands that carry elements use UIElement[] as their type on both the C# and TypeScript sides. Before this design, Component was a separate class that implemented IComponent and had to be converted to a wire-format plain object before assignment (toWireElement). That conversion was a callsite smell that hid serialization failures and required every consumer to know about it.

Now Component extends ExternalComponent implements IComponent. An InputText instance IS a UIElement — pass it directly:

command.elements = updatedElements as unknown as typeof command.elements;

The as unknown as cast is still needed because TypeScript sees UIElement[] (with id: Guid) while the editor's PrototypeSurfaceElement type has id: string. The runtime values are correct; only the declared types diverge at this boundary.

How serialization works

The Fundamentals JsonSerializer walks Object.getOwnPropertyNames(instance) and serializes each own property. The design of Component ensures only JSON-safe values appear as own properties:

| What | Own property? | Why | |---|---|---| | id | Yes — string accessor via Object.defineProperty | C# System.Text.Json parses a UUID string as Guid automatically | | ZIndex, anchoring, width, height, name, properties, slots, componentName | Yes — class fields set in initialize() | All are plain values; safely serialized | | _derivedTypeId | Yes — set explicitly in initialize() | Tells Fundamentals to deserialize as ExternalComponent on the server | | toolbarIcon | No — prototype getter | React-icons components are regular functions with a prototype.constructor self-reference; serializing them causes infinite recursion. Being a prototype getter keeps them out of Object.getOwnPropertyNames entirely | | RenderPrototype | No — abstract prototype method | Same reason; a method on the prototype is never an own property of the instance | | label, position, size, children, type | No — prototype getters | Computed bridges; not serializable data |

The id bridge

SceneObject declares id: Guid (via @field(Guid)). IComponent declares id: string. The constructor replaces the class-field own property with a plain-string accessor:

let _id = '';
Object.defineProperty(this, 'id', {
    get() { return _id; },
    set(value: string) { _id = String(value ?? ''); },
    enumerable: true,
    configurable: true,
});

The serializer reads the string UUID; C# parses it as Guid. Editor code that compares IDs uses string equality throughout.

The _derivedTypeId own property

DerivedType.get(constructor) uses Reflect.getOwnMetadata, which does not traverse the prototype chain. The @derivedType('externalComponent') decorator is on ExternalComponent, so DerivedType.get(InputText) returns undefined. Without the explicit own property the JSON output would have no _derivedTypeId and the C# deserializer would not know to construct an ExternalComponent.

initialize() sets it directly:

(this as unknown as Record<string, unknown>)['_derivedTypeId'] = 'externalComponent';

The type key

After initialize(), componentName is updated to the full library-qualified key ('PrimeReact.InputText'). Before initialize() the subclass class field holds the short name ('InputText'). The type getter always returns the fully qualified key by splitting on . and re-joining with library:

get type(): string {
    const cn = this.componentName ?? '';
    const shortName = cn.split('.').pop() ?? cn;
    return this.library ? `${this.library}.${shortName}` : shortName;
}

Subclass authoring

Implement these members:

export default class InputText extends Component {
    readonly componentName = 'InputText'; // short name; initialize() stores the full key
    readonly category = 'Form';
    readonly library = 'PrimeReact';
    readonly initialSize = { width: 220, height: 42 };
    readonly resizeBehavior = 'horizontal' as const;

    // MUST be a getter — not a class field.
    // Class fields are own instance properties and cause infinite recursion in Fundamentals' serializer.
    override get toolbarIcon() { return FaKeyboard; }

    RenderPrototype({ label }: PrototypeComponentRenderProps): ReactNode { ... }
}

toolbarIcon must be an override get. A class field (readonly toolbarIcon = FaKeyboard) makes FaKeyboard an own instance property. Fundamentals' serializer then calls Object.getOwnPropertyNames(FaKeyboard) which includes prototype, whose constructor points back to FaKeyboard, causing unbounded recursion and a RangeError.