@ankhorage/contracts 0.2.0 → 0.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @ankhorage/contracts
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9e27c89: Add app category theme recommendation contracts and a partial category recommendation map for theme tooling.
8
+
3
9
  ## 0.2.0
4
10
 
5
11
  ### Minor Changes
package/README.md CHANGED
@@ -22,7 +22,13 @@ Shared public contracts for Ankhorage packages and standalone provider packages.
22
22
  ```ts
23
23
  import type { AppManifest } from '@ankhorage/contracts';
24
24
  import type { AuthAdapter } from '@ankhorage/contracts/auth';
25
- import type { AppMood, ColorHarmony, ColorTone } from '@ankhorage/contracts/color-theory';
25
+ import type {
26
+ AppCategoryThemeRecommendation,
27
+ AppMood,
28
+ ColorHarmony,
29
+ ColorTone,
30
+ } from '@ankhorage/contracts/color-theory';
31
+ import { APP_CATEGORY_THEME_RECOMMENDATIONS } from '@ankhorage/contracts/color-theory';
26
32
  import type { DbAdapter } from '@ankhorage/contracts/db';
27
33
  ```
28
34
 
@@ -34,6 +40,17 @@ import type { DbAdapter } from '@ankhorage/contracts/db';
34
40
 
35
41
  `ColorHarmony` describes hue relationships.
36
42
 
43
+ Category-aware theme recommendations can suggest an app mood, color tone,
44
+ harmony, and optional primary hue without moving UI color-generation logic into
45
+ Contracts:
46
+
47
+ ```ts
48
+ const financeThemeRecommendation = APP_CATEGORY_THEME_RECOMMENDATIONS.finance_money;
49
+ ```
50
+
51
+ These recommendation contracts are serializable handoff data for tooling. OKLCH,
52
+ chroma, and semantic-token generation stay in UI/theme packages.
53
+
37
54
  Provider packages can implement the shared contracts without importing runtime,
38
55
  CLI, ZORA, Expo Router, or app-generation logic.
39
56
 
@@ -1,3 +1,4 @@
1
+ import type { AppCategory } from './types';
1
2
  export declare const COLOR_HARMONIES: readonly ["monochromatic", "analogous", "complementary", "triadic", "tetradic", "splitComplementary"];
2
3
  export type ColorHarmony = (typeof COLOR_HARMONIES)[number];
3
4
  export declare const COLOR_TONES: readonly ["neutral", "pastel", "earth", "mineral", "muted", "jewel", "fluorescent", "obsidian", "vaporwave", "monochromeAccent"];
@@ -14,4 +15,44 @@ export interface ColorThemeRecommendation {
14
15
  suggestedHarmony: ColorHarmony;
15
16
  suggestedPrimaryHueDegrees?: number;
16
17
  }
18
+ export interface AppCategoryThemeRecommendation extends ColorThemeRecommendation {
19
+ appCategory: AppCategory;
20
+ }
21
+ export declare const APP_CATEGORY_THEME_RECOMMENDATIONS: {
22
+ readonly finance_money: {
23
+ readonly appCategory: "finance_money";
24
+ readonly appMood: "trustworthy";
25
+ readonly suggestedColorTone: "mineral";
26
+ readonly suggestedHarmony: "analogous";
27
+ readonly suggestedPrimaryHueDegrees: 195;
28
+ };
29
+ readonly health_fitness: {
30
+ readonly appCategory: "health_fitness";
31
+ readonly appMood: "calm";
32
+ readonly suggestedColorTone: "pastel";
33
+ readonly suggestedHarmony: "analogous";
34
+ readonly suggestedPrimaryHueDegrees: 155;
35
+ };
36
+ readonly developer_tools: {
37
+ readonly appCategory: "developer_tools";
38
+ readonly appMood: "focused";
39
+ readonly suggestedColorTone: "obsidian";
40
+ readonly suggestedHarmony: "analogous";
41
+ readonly suggestedPrimaryHueDegrees: 220;
42
+ };
43
+ readonly graphics_design: {
44
+ readonly appCategory: "graphics_design";
45
+ readonly appMood: "creative";
46
+ readonly suggestedColorTone: "vaporwave";
47
+ readonly suggestedHarmony: "splitComplementary";
48
+ readonly suggestedPrimaryHueDegrees: 305;
49
+ };
50
+ readonly social_community: {
51
+ readonly appCategory: "social_community";
52
+ readonly appMood: "friendly";
53
+ readonly suggestedColorTone: "jewel";
54
+ readonly suggestedHarmony: "triadic";
55
+ readonly suggestedPrimaryHueDegrees: 265;
56
+ };
57
+ };
17
58
  //# sourceMappingURL=color-theory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"color-theory.d.ts","sourceRoot":"","sources":["../src/color-theory.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,uGAOlB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5D,eAAO,MAAM,WAAW,kIAWd,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,eAAO,MAAM,SAAS,gIAWZ,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjD,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,SAAS,CAAC;IAC1B,cAAc,EAAE,SAAS,CAAC;CAC3B;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,EAAE,SAAS,CAAC;IAC9B,gBAAgB,EAAE,YAAY,CAAC;IAC/B,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC"}
1
+ {"version":3,"file":"color-theory.d.ts","sourceRoot":"","sources":["../src/color-theory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,eAAO,MAAM,eAAe,uGAOlB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5D,eAAO,MAAM,WAAW,kIAWd,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,eAAO,MAAM,SAAS,gIAWZ,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjD,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,SAAS,CAAC;IAC1B,cAAc,EAAE,SAAS,CAAC;CAC3B;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,EAAE,SAAS,CAAC;IAC9B,gBAAgB,EAAE,YAAY,CAAC;IAC/B,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,MAAM,WAAW,8BAA+B,SAAQ,wBAAwB;IAC9E,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,eAAO,MAAM,kCAAkC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCkC,CAAC"}
@@ -30,4 +30,41 @@ export const APP_MOODS = [
30
30
  'friendly',
31
31
  'technical',
32
32
  ];
33
+ export const APP_CATEGORY_THEME_RECOMMENDATIONS = {
34
+ finance_money: {
35
+ appCategory: 'finance_money',
36
+ appMood: 'trustworthy',
37
+ suggestedColorTone: 'mineral',
38
+ suggestedHarmony: 'analogous',
39
+ suggestedPrimaryHueDegrees: 195,
40
+ },
41
+ health_fitness: {
42
+ appCategory: 'health_fitness',
43
+ appMood: 'calm',
44
+ suggestedColorTone: 'pastel',
45
+ suggestedHarmony: 'analogous',
46
+ suggestedPrimaryHueDegrees: 155,
47
+ },
48
+ developer_tools: {
49
+ appCategory: 'developer_tools',
50
+ appMood: 'focused',
51
+ suggestedColorTone: 'obsidian',
52
+ suggestedHarmony: 'analogous',
53
+ suggestedPrimaryHueDegrees: 220,
54
+ },
55
+ graphics_design: {
56
+ appCategory: 'graphics_design',
57
+ appMood: 'creative',
58
+ suggestedColorTone: 'vaporwave',
59
+ suggestedHarmony: 'splitComplementary',
60
+ suggestedPrimaryHueDegrees: 305,
61
+ },
62
+ social_community: {
63
+ appCategory: 'social_community',
64
+ appMood: 'friendly',
65
+ suggestedColorTone: 'jewel',
66
+ suggestedHarmony: 'triadic',
67
+ suggestedPrimaryHueDegrees: 265,
68
+ },
69
+ };
33
70
  //# sourceMappingURL=color-theory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"color-theory.js","sourceRoot":"","sources":["../src/color-theory.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,eAAe;IACf,WAAW;IACX,eAAe;IACf,SAAS;IACT,UAAU;IACV,oBAAoB;CACZ,CAAC;AAIX,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,SAAS;IACT,QAAQ;IACR,OAAO;IACP,SAAS;IACT,OAAO;IACP,OAAO;IACP,aAAa;IACb,UAAU;IACV,WAAW;IACX,kBAAkB;CACV,CAAC;AAIX,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,aAAa;IACb,MAAM;IACN,SAAS;IACT,SAAS;IACT,SAAS;IACT,WAAW;IACX,SAAS;IACT,UAAU;IACV,UAAU;IACV,WAAW;CACH,CAAC","sourcesContent":["export const COLOR_HARMONIES = [\n 'monochromatic',\n 'analogous',\n 'complementary',\n 'triadic',\n 'tetradic',\n 'splitComplementary',\n] as const;\n\nexport type ColorHarmony = (typeof COLOR_HARMONIES)[number];\n\nexport const COLOR_TONES = [\n 'neutral',\n 'pastel',\n 'earth',\n 'mineral',\n 'muted',\n 'jewel',\n 'fluorescent',\n 'obsidian',\n 'vaporwave',\n 'monochromeAccent',\n] as const;\n\nexport type ColorTone = (typeof COLOR_TONES)[number];\n\nexport const APP_MOODS = [\n 'trustworthy',\n 'calm',\n 'focused',\n 'playful',\n 'premium',\n 'energetic',\n 'serious',\n 'creative',\n 'friendly',\n 'technical',\n] as const;\n\nexport type AppMood = (typeof APP_MOODS)[number];\n\nexport interface ColorToneLaneRecipe {\n backgroundTone: ColorTone;\n foregroundTone: ColorTone;\n}\n\nexport interface ColorThemeRecommendation {\n appMood: AppMood;\n suggestedColorTone: ColorTone;\n suggestedHarmony: ColorHarmony;\n suggestedPrimaryHueDegrees?: number;\n}\n"]}
1
+ {"version":3,"file":"color-theory.js","sourceRoot":"","sources":["../src/color-theory.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,eAAe;IACf,WAAW;IACX,eAAe;IACf,SAAS;IACT,UAAU;IACV,oBAAoB;CACZ,CAAC;AAIX,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,SAAS;IACT,QAAQ;IACR,OAAO;IACP,SAAS;IACT,OAAO;IACP,OAAO;IACP,aAAa;IACb,UAAU;IACV,WAAW;IACX,kBAAkB;CACV,CAAC;AAIX,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,aAAa;IACb,MAAM;IACN,SAAS;IACT,SAAS;IACT,SAAS;IACT,WAAW;IACX,SAAS;IACT,UAAU;IACV,UAAU;IACV,WAAW;CACH,CAAC;AAoBX,MAAM,CAAC,MAAM,kCAAkC,GAAG;IAChD,aAAa,EAAE;QACb,WAAW,EAAE,eAAe;QAC5B,OAAO,EAAE,aAAa;QACtB,kBAAkB,EAAE,SAAS;QAC7B,gBAAgB,EAAE,WAAW;QAC7B,0BAA0B,EAAE,GAAG;KAChC;IACD,cAAc,EAAE;QACd,WAAW,EAAE,gBAAgB;QAC7B,OAAO,EAAE,MAAM;QACf,kBAAkB,EAAE,QAAQ;QAC5B,gBAAgB,EAAE,WAAW;QAC7B,0BAA0B,EAAE,GAAG;KAChC;IACD,eAAe,EAAE;QACf,WAAW,EAAE,iBAAiB;QAC9B,OAAO,EAAE,SAAS;QAClB,kBAAkB,EAAE,UAAU;QAC9B,gBAAgB,EAAE,WAAW;QAC7B,0BAA0B,EAAE,GAAG;KAChC;IACD,eAAe,EAAE;QACf,WAAW,EAAE,iBAAiB;QAC9B,OAAO,EAAE,UAAU;QACnB,kBAAkB,EAAE,WAAW;QAC/B,gBAAgB,EAAE,oBAAoB;QACtC,0BAA0B,EAAE,GAAG;KAChC;IACD,gBAAgB,EAAE;QAChB,WAAW,EAAE,kBAAkB;QAC/B,OAAO,EAAE,UAAU;QACnB,kBAAkB,EAAE,OAAO;QAC3B,gBAAgB,EAAE,SAAS;QAC3B,0BAA0B,EAAE,GAAG;KAChC;CAC8E,CAAC","sourcesContent":["import type { AppCategory } from './types';\n\nexport const COLOR_HARMONIES = [\n 'monochromatic',\n 'analogous',\n 'complementary',\n 'triadic',\n 'tetradic',\n 'splitComplementary',\n] as const;\n\nexport type ColorHarmony = (typeof COLOR_HARMONIES)[number];\n\nexport const COLOR_TONES = [\n 'neutral',\n 'pastel',\n 'earth',\n 'mineral',\n 'muted',\n 'jewel',\n 'fluorescent',\n 'obsidian',\n 'vaporwave',\n 'monochromeAccent',\n] as const;\n\nexport type ColorTone = (typeof COLOR_TONES)[number];\n\nexport const APP_MOODS = [\n 'trustworthy',\n 'calm',\n 'focused',\n 'playful',\n 'premium',\n 'energetic',\n 'serious',\n 'creative',\n 'friendly',\n 'technical',\n] as const;\n\nexport type AppMood = (typeof APP_MOODS)[number];\n\nexport interface ColorToneLaneRecipe {\n backgroundTone: ColorTone;\n foregroundTone: ColorTone;\n}\n\nexport interface ColorThemeRecommendation {\n appMood: AppMood;\n suggestedColorTone: ColorTone;\n suggestedHarmony: ColorHarmony;\n suggestedPrimaryHueDegrees?: number;\n}\n\nexport interface AppCategoryThemeRecommendation extends ColorThemeRecommendation {\n appCategory: AppCategory;\n}\n\nexport const APP_CATEGORY_THEME_RECOMMENDATIONS = {\n finance_money: {\n appCategory: 'finance_money',\n appMood: 'trustworthy',\n suggestedColorTone: 'mineral',\n suggestedHarmony: 'analogous',\n suggestedPrimaryHueDegrees: 195,\n },\n health_fitness: {\n appCategory: 'health_fitness',\n appMood: 'calm',\n suggestedColorTone: 'pastel',\n suggestedHarmony: 'analogous',\n suggestedPrimaryHueDegrees: 155,\n },\n developer_tools: {\n appCategory: 'developer_tools',\n appMood: 'focused',\n suggestedColorTone: 'obsidian',\n suggestedHarmony: 'analogous',\n suggestedPrimaryHueDegrees: 220,\n },\n graphics_design: {\n appCategory: 'graphics_design',\n appMood: 'creative',\n suggestedColorTone: 'vaporwave',\n suggestedHarmony: 'splitComplementary',\n suggestedPrimaryHueDegrees: 305,\n },\n social_community: {\n appCategory: 'social_community',\n appMood: 'friendly',\n suggestedColorTone: 'jewel',\n suggestedHarmony: 'triadic',\n suggestedPrimaryHueDegrees: 265,\n },\n} as const satisfies Partial<Record<AppCategory, AppCategoryThemeRecommendation>>;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/contracts",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "main": "./dist/index.js",
5
5
  "devDependencies": {
6
6
  "@ankhorage/devtools": "^1.0.0",
@@ -1,3 +1,5 @@
1
+ import type { AppCategory } from './types';
2
+
1
3
  export const COLOR_HARMONIES = [
2
4
  'monochromatic',
3
5
  'analogous',
@@ -50,3 +52,45 @@ export interface ColorThemeRecommendation {
50
52
  suggestedHarmony: ColorHarmony;
51
53
  suggestedPrimaryHueDegrees?: number;
52
54
  }
55
+
56
+ export interface AppCategoryThemeRecommendation extends ColorThemeRecommendation {
57
+ appCategory: AppCategory;
58
+ }
59
+
60
+ export const APP_CATEGORY_THEME_RECOMMENDATIONS = {
61
+ finance_money: {
62
+ appCategory: 'finance_money',
63
+ appMood: 'trustworthy',
64
+ suggestedColorTone: 'mineral',
65
+ suggestedHarmony: 'analogous',
66
+ suggestedPrimaryHueDegrees: 195,
67
+ },
68
+ health_fitness: {
69
+ appCategory: 'health_fitness',
70
+ appMood: 'calm',
71
+ suggestedColorTone: 'pastel',
72
+ suggestedHarmony: 'analogous',
73
+ suggestedPrimaryHueDegrees: 155,
74
+ },
75
+ developer_tools: {
76
+ appCategory: 'developer_tools',
77
+ appMood: 'focused',
78
+ suggestedColorTone: 'obsidian',
79
+ suggestedHarmony: 'analogous',
80
+ suggestedPrimaryHueDegrees: 220,
81
+ },
82
+ graphics_design: {
83
+ appCategory: 'graphics_design',
84
+ appMood: 'creative',
85
+ suggestedColorTone: 'vaporwave',
86
+ suggestedHarmony: 'splitComplementary',
87
+ suggestedPrimaryHueDegrees: 305,
88
+ },
89
+ social_community: {
90
+ appCategory: 'social_community',
91
+ appMood: 'friendly',
92
+ suggestedColorTone: 'jewel',
93
+ suggestedHarmony: 'triadic',
94
+ suggestedPrimaryHueDegrees: 265,
95
+ },
96
+ } as const satisfies Partial<Record<AppCategory, AppCategoryThemeRecommendation>>;
@@ -2,6 +2,8 @@ import { describe, expect, it } from 'bun:test';
2
2
 
3
3
  import {
4
4
  APP_CATEGORIES,
5
+ APP_CATEGORY_THEME_RECOMMENDATIONS,
6
+ APP_MOODS,
5
7
  type AppCategory,
6
8
  AUTH_PROVIDERS,
7
9
  AUTH_SIGN_IN_IDENTIFIERS,
@@ -9,6 +11,8 @@ import {
9
11
  type AuthAdapter,
10
12
  type AuthFlowConfig,
11
13
  type AuthSpec,
14
+ COLOR_HARMONIES,
15
+ COLOR_TONES,
12
16
  type DbAdapter,
13
17
  DEPLOYMENT_TARGETS,
14
18
  NAVIGATOR_TYPES,
@@ -73,6 +77,27 @@ describe('contracts', () => {
73
77
  expect(theme.dark.colorTone).toBe('neutral');
74
78
  });
75
79
 
80
+ it('exports app category theme recommendations with valid serialized values', () => {
81
+ const recommendations = Object.values(APP_CATEGORY_THEME_RECOMMENDATIONS);
82
+
83
+ expect(recommendations.length).toBeGreaterThan(0);
84
+
85
+ for (const recommendation of recommendations) {
86
+ expect(APP_CATEGORIES).toContain(recommendation.appCategory);
87
+ expect(APP_MOODS).toContain(recommendation.appMood);
88
+ expect(COLOR_TONES).toContain(recommendation.suggestedColorTone);
89
+ expect(COLOR_HARMONIES).toContain(recommendation.suggestedHarmony);
90
+ }
91
+ });
92
+
93
+ it('keeps app category theme recommendation hues in range', () => {
94
+ for (const recommendation of Object.values(APP_CATEGORY_THEME_RECOMMENDATIONS)) {
95
+ expect(Number.isFinite(recommendation.suggestedPrimaryHueDegrees)).toBe(true);
96
+ expect(recommendation.suggestedPrimaryHueDegrees).toBeGreaterThanOrEqual(0);
97
+ expect(recommendation.suggestedPrimaryHueDegrees).toBeLessThan(360);
98
+ }
99
+ });
100
+
76
101
  it('accepts canonical auth flow config without legacy route fields', () => {
77
102
  const authFlow: AuthFlowConfig = {
78
103
  signInRoute: '/sign-in',
@@ -147,20 +172,20 @@ describe('contracts', () => {
147
172
  const dbAdapter: DbAdapter = {
148
173
  async select() {
149
174
  await new Promise((resolve) => setTimeout(resolve, 1));
150
- return { ok: true, data: [{ id: 'row-1' }] };
175
+ return { ok: true, data: [] };
151
176
  },
152
177
  async findById() {
153
178
  await new Promise((resolve) => setTimeout(resolve, 1));
154
- return { ok: true, data: { id: 'row-1' } };
179
+ return { ok: true, data: null };
155
180
  },
156
181
  async insert(input) {
157
182
  const values = Array.isArray(input.values) ? input.values : [input.values];
158
183
  await new Promise((resolve) => setTimeout(resolve, 1));
159
184
  return { ok: true, data: values };
160
185
  },
161
- async update(input) {
186
+ async update() {
162
187
  await new Promise((resolve) => setTimeout(resolve, 1));
163
- return { ok: true, data: [input.values] };
188
+ return { ok: true, data: [] };
164
189
  },
165
190
  async delete() {
166
191
  await new Promise((resolve) => setTimeout(resolve, 1));