@paul-portfolio/work-portfolio-contract 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,79 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The work-portfolio catalog: past projects and the demoable features under
4
+ * them. The remote owns it and serves it as catalog.json; the host only ever
5
+ * reads it through this schema.
6
+ */
7
+ /** Visual accent each project's demos carry inside the stage. */
8
+ export declare const AccentThemeSchema: z.ZodObject<{
9
+ accent: z.ZodString;
10
+ surface: z.ZodString;
11
+ font: z.ZodEnum<{
12
+ sans: "sans";
13
+ mono: "mono";
14
+ }>;
15
+ }, z.core.$strip>;
16
+ /** One of the past projects. Names are public-safe, never the real ones. */
17
+ export declare const WorkProjectSchema: z.ZodObject<{
18
+ id: z.ZodString;
19
+ name: z.ZodString;
20
+ blurb: z.ZodString;
21
+ stack: z.ZodString;
22
+ accent: z.ZodObject<{
23
+ accent: z.ZodString;
24
+ surface: z.ZodString;
25
+ font: z.ZodEnum<{
26
+ sans: "sans";
27
+ mono: "mono";
28
+ }>;
29
+ }, z.core.$strip>;
30
+ cutFeatures: z.ZodArray<z.ZodString>;
31
+ }, z.core.$strip>;
32
+ /** One demoable feature, shown in the bottom ticker. */
33
+ export declare const WorkFeatureSchema: z.ZodObject<{
34
+ slug: z.ZodString;
35
+ projectId: z.ZodString;
36
+ title: z.ZodString;
37
+ tagline: z.ZodString;
38
+ icon: z.ZodString;
39
+ flagship: z.ZodOptional<z.ZodBoolean>;
40
+ explainer: z.ZodObject<{
41
+ did: z.ZodString;
42
+ stack: z.ZodString;
43
+ mocked: z.ZodString;
44
+ }, z.core.$strip>;
45
+ }, z.core.$strip>;
46
+ export declare const CatalogSchema: z.ZodObject<{
47
+ projects: z.ZodArray<z.ZodObject<{
48
+ id: z.ZodString;
49
+ name: z.ZodString;
50
+ blurb: z.ZodString;
51
+ stack: z.ZodString;
52
+ accent: z.ZodObject<{
53
+ accent: z.ZodString;
54
+ surface: z.ZodString;
55
+ font: z.ZodEnum<{
56
+ sans: "sans";
57
+ mono: "mono";
58
+ }>;
59
+ }, z.core.$strip>;
60
+ cutFeatures: z.ZodArray<z.ZodString>;
61
+ }, z.core.$strip>>;
62
+ features: z.ZodArray<z.ZodObject<{
63
+ slug: z.ZodString;
64
+ projectId: z.ZodString;
65
+ title: z.ZodString;
66
+ tagline: z.ZodString;
67
+ icon: z.ZodString;
68
+ flagship: z.ZodOptional<z.ZodBoolean>;
69
+ explainer: z.ZodObject<{
70
+ did: z.ZodString;
71
+ stack: z.ZodString;
72
+ mocked: z.ZodString;
73
+ }, z.core.$strip>;
74
+ }, z.core.$strip>>;
75
+ }, z.core.$strip>;
76
+ export type AccentTheme = z.infer<typeof AccentThemeSchema>;
77
+ export type WorkProject = z.infer<typeof WorkProjectSchema>;
78
+ export type WorkFeature = z.infer<typeof WorkFeatureSchema>;
79
+ export type Catalog = z.infer<typeof CatalogSchema>;
@@ -0,0 +1,75 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The work-portfolio catalog: past projects and the demoable features under
4
+ * them. The remote owns it and serves it as catalog.json; the host only ever
5
+ * reads it through this schema.
6
+ */
7
+ /** Visual accent each project's demos carry inside the stage. */
8
+ export const AccentThemeSchema = z.object({
9
+ /** primary accent color, hex */
10
+ accent: z.string(),
11
+ /** translucent surface tint used behind demo content */
12
+ surface: z.string(),
13
+ /** typography flavor for the demo surface */
14
+ font: z.enum(["sans", "mono"]),
15
+ });
16
+ /** One of the past projects. Names are public-safe, never the real ones. */
17
+ export const WorkProjectSchema = z.object({
18
+ id: z.string().min(1),
19
+ /** anonymized public name shown in the top ticker */
20
+ name: z.string().min(1),
21
+ /** one-liner for the explainer window */
22
+ blurb: z.string(),
23
+ /** original stack, described without identifying details */
24
+ stack: z.string(),
25
+ accent: AccentThemeSchema,
26
+ /** features that did not make the ticker, listed in the explainer */
27
+ cutFeatures: z.array(z.string()),
28
+ });
29
+ /** One demoable feature, shown in the bottom ticker. */
30
+ export const WorkFeatureSchema = z.object({
31
+ /** url-safe id used for ?feature= deep links, so it never changes once shipped */
32
+ slug: z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
33
+ projectId: z.string().min(1),
34
+ title: z.string().min(1),
35
+ /** short line shown on the chip and stage header */
36
+ tagline: z.string(),
37
+ /** small emoji icon shown on the chip */
38
+ icon: z.string(),
39
+ /** flagship demos get more depth than vignettes */
40
+ flagship: z.boolean().optional(),
41
+ explainer: z.object({
42
+ /** what the feature did in the original app */
43
+ did: z.string(),
44
+ /** what it was built with originally */
45
+ stack: z.string(),
46
+ /** what is real vs faked in this reconstruction */
47
+ mocked: z.string(),
48
+ }),
49
+ });
50
+ export const CatalogSchema = z
51
+ .object({
52
+ projects: z.array(WorkProjectSchema),
53
+ features: z.array(WorkFeatureSchema),
54
+ })
55
+ .superRefine((catalog, ctx) => {
56
+ const projectIds = new Set(catalog.projects.map((p) => p.id));
57
+ const seen = new Set();
58
+ catalog.features.forEach((feature, index) => {
59
+ if (seen.has(feature.slug)) {
60
+ ctx.addIssue({
61
+ code: "custom",
62
+ path: ["features", index, "slug"],
63
+ message: `duplicate slug "${feature.slug}"`,
64
+ });
65
+ }
66
+ seen.add(feature.slug);
67
+ if (!projectIds.has(feature.projectId)) {
68
+ ctx.addIssue({
69
+ code: "custom",
70
+ path: ["features", index, "projectId"],
71
+ message: `unknown project "${feature.projectId}"`,
72
+ });
73
+ }
74
+ });
75
+ });
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Everything that crosses the boundary between paul-explore (the host) and
3
+ * the work-portfolio remote. Both sides type-check against this package, and
4
+ * nothing else is shared between them except the design-system tokens.
5
+ */
6
+ export { AccentThemeSchema, WorkProjectSchema, WorkFeatureSchema, CatalogSchema, type AccentTheme, type WorkProject, type WorkFeature, type Catalog, } from "./catalog.js";
7
+ /**
8
+ * The contract's major version. The host refuses a remote built against a
9
+ * different major and shows its fallback instead, so a breaking change ships
10
+ * as: host learns both majors, remote moves, host drops the old one.
11
+ */
12
+ export declare const CONTRACT_VERSION = 1;
13
+ /** Whether a remote's declared contract version is one this build can mount. */
14
+ export declare function isCompatibleContract(version: unknown): boolean;
15
+ /** A referral link, as portfolio_api returns it. */
16
+ export type Referral = {
17
+ slug: string;
18
+ targetPath: string;
19
+ label: string | null;
20
+ url: string;
21
+ clicks: number;
22
+ createdAt: string;
23
+ };
24
+ export type ReferralStats = {
25
+ slug: string;
26
+ targetPath: string;
27
+ clicks: number;
28
+ recent: {
29
+ at: string;
30
+ }[];
31
+ };
32
+ export type CreateReferralInput = {
33
+ slug?: string;
34
+ targetPath?: string;
35
+ label?: string;
36
+ };
37
+ /**
38
+ * Capabilities the host lends the remote. The remote never learns an API URL
39
+ * or touches auth: it asks for data through these and the host decides how to
40
+ * fetch it. An API refusal rejects with an Error whose message is fit to show
41
+ * a user; an unreachable API rejects with a TypeError, the way fetch does, so
42
+ * the remote can tell "no" apart from "offline".
43
+ */
44
+ export type HostServices = {
45
+ referrals: {
46
+ create(input: CreateReferralInput): Promise<Referral>;
47
+ stats(slug: string): Promise<ReferralStats>;
48
+ recordClick(slug: string): Promise<{
49
+ slug: string;
50
+ clicks: number;
51
+ }>;
52
+ };
53
+ };
54
+ /**
55
+ * What the host hands the remote on mount. The host owns the URL: the remote
56
+ * reads the starting feature from here and reports changes back, and never
57
+ * writes to history itself.
58
+ */
59
+ export type HostContext = {
60
+ /** slug from ?feature=, or null for the intro card */
61
+ initialFeature: string | null;
62
+ /** called whenever the selected feature changes; null means the intro card */
63
+ onFeatureChange(slug: string | null): void;
64
+ services: HostServices;
65
+ };
66
+ export type MountHandle = {
67
+ /** re-render with part of the context replaced, e.g. a new initialFeature */
68
+ update(next: Partial<HostContext>): void;
69
+ /** tear everything down: React root, listeners, timers */
70
+ unmount(): void;
71
+ };
72
+ /** The shape of the module the remote exposes as "./mount". */
73
+ export type RemoteModule = {
74
+ contractVersion: number;
75
+ /** the remote's own release, e.g. "1.4.0" */
76
+ version: string;
77
+ mount(el: HTMLElement, ctx: HostContext): MountHandle;
78
+ };
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Everything that crosses the boundary between paul-explore (the host) and
3
+ * the work-portfolio remote. Both sides type-check against this package, and
4
+ * nothing else is shared between them except the design-system tokens.
5
+ */
6
+ export { AccentThemeSchema, WorkProjectSchema, WorkFeatureSchema, CatalogSchema, } from "./catalog.js";
7
+ /**
8
+ * The contract's major version. The host refuses a remote built against a
9
+ * different major and shows its fallback instead, so a breaking change ships
10
+ * as: host learns both majors, remote moves, host drops the old one.
11
+ */
12
+ export const CONTRACT_VERSION = 1;
13
+ /** Whether a remote's declared contract version is one this build can mount. */
14
+ export function isCompatibleContract(version) {
15
+ return typeof version === "number" && Math.floor(version) === CONTRACT_VERSION;
16
+ }
package/package.json CHANGED
@@ -1,6 +1,42 @@
1
1
  {
2
2
  "name": "@paul-portfolio/work-portfolio-contract",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "The boundary between paul-explore and the work-portfolio micro-frontend: mount types, host services, and the catalog schema.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/gpbsumido/work-portfolio-mfe.git",
10
+ "directory": "packages/contract"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js"
21
+ }
22
+ },
23
+ "sideEffects": false,
24
+ "scripts": {
25
+ "build": "tsc -p tsconfig.build.json",
26
+ "typecheck": "tsc -p tsconfig.json --noEmit",
27
+ "test": "vitest run"
28
+ },
29
+ "peerDependencies": {
30
+ "zod": "^4.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^20.19.43",
34
+ "typescript": "^5.9.3",
35
+ "vitest": "^4.1.2",
36
+ "zod": "^4.3.6"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public",
40
+ "provenance": true
41
+ }
42
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.