@vedangiitb/qwintly-core 1.3.7 → 1.3.9

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.
Files changed (63) hide show
  1. package/dist/ai/prompts/codegen.prompt.d.ts.map +1 -1
  2. package/dist/ai/prompts/codegen.prompt.js +37 -39
  3. package/dist/ai/prompts/codegen.prompt.js.map +1 -1
  4. package/dist/ai/prompts/examples/codegen.examples.js +1 -1
  5. package/dist/ai/prompts/examples/codegen.examples.js.map +1 -1
  6. package/dist/ai/prompts/examples/planner.examples.d.ts.map +1 -1
  7. package/dist/ai/prompts/examples/planner.examples.js +6 -1
  8. package/dist/ai/prompts/examples/planner.examples.js.map +1 -1
  9. package/dist/ai/prompts/helpers/promptParts.helper.d.ts +1 -3
  10. package/dist/ai/prompts/helpers/promptParts.helper.d.ts.map +1 -1
  11. package/dist/ai/prompts/helpers/promptParts.helper.js +9 -18
  12. package/dist/ai/prompts/helpers/promptParts.helper.js.map +1 -1
  13. package/dist/ai/prompts/planner.prompt.d.ts.map +1 -1
  14. package/dist/ai/prompts/planner.prompt.js +32 -36
  15. package/dist/ai/prompts/planner.prompt.js.map +1 -1
  16. package/dist/ai/prompts/validator.prompt.d.ts.map +1 -1
  17. package/dist/ai/prompts/validator.prompt.js +38 -39
  18. package/dist/ai/prompts/validator.prompt.js.map +1 -1
  19. package/dist/ai/toolLoop/toolLoopContext.js +1 -1
  20. package/dist/ai/toolLoop/toolLoopContext.js.map +1 -1
  21. package/dist/ai/toolLoop/toolLoopRunner.d.ts.map +1 -1
  22. package/dist/ai/toolLoop/toolLoopRunner.js +22 -15
  23. package/dist/ai/toolLoop/toolLoopRunner.js.map +1 -1
  24. package/dist/ai/toolLoop/toolLoopRunnerUtils.d.ts.map +1 -1
  25. package/dist/ai/toolLoop/toolLoopRunnerUtils.js +13 -0
  26. package/dist/ai/toolLoop/toolLoopRunnerUtils.js.map +1 -1
  27. package/dist/ai/tools/implementations/createNewRoute.impl.d.ts +13 -1
  28. package/dist/ai/tools/implementations/createNewRoute.impl.d.ts.map +1 -1
  29. package/dist/ai/tools/implementations/createNewRoute.impl.js +98 -29
  30. package/dist/ai/tools/implementations/createNewRoute.impl.js.map +1 -1
  31. package/dist/ai/tools/implementations/factories.d.ts +17 -2
  32. package/dist/ai/tools/implementations/factories.d.ts.map +1 -1
  33. package/dist/ai/tools/implementations/insertElement.impl.d.ts.map +1 -1
  34. package/dist/ai/tools/implementations/insertElement.impl.js +1 -1
  35. package/dist/ai/tools/implementations/insertElement.impl.js.map +1 -1
  36. package/dist/ai/tools/implementations/readFile.impl.d.ts +4 -1
  37. package/dist/ai/tools/implementations/readFile.impl.d.ts.map +1 -1
  38. package/dist/ai/tools/implementations/readFile.impl.js +10 -0
  39. package/dist/ai/tools/implementations/readFile.impl.js.map +1 -1
  40. package/dist/ai/tools/schemas/createNewRoute.schema.js +1 -1
  41. package/dist/ai/tools/schemas/createNewRoute.schema.js.map +1 -1
  42. package/dist/ai/tools/schemas/insertElement.schema.d.ts.map +1 -1
  43. package/dist/ai/tools/schemas/insertElement.schema.js +7 -7
  44. package/dist/ai/tools/schemas/insertElement.schema.js.map +1 -1
  45. package/dist/ai/tools/schemas/updateProps.schema.js +1 -1
  46. package/dist/ai/tools/schemas/updateProps.schema.js.map +1 -1
  47. package/dist/image/unsplash.service.d.ts.map +1 -1
  48. package/dist/image/unsplash.service.js +1 -0
  49. package/dist/image/unsplash.service.js.map +1 -1
  50. package/dist/indexer/data/configs.constants.d.ts +21 -28
  51. package/dist/indexer/data/configs.constants.d.ts.map +1 -1
  52. package/dist/indexer/data/configs.constants.js +34 -45
  53. package/dist/indexer/data/configs.constants.js.map +1 -1
  54. package/dist/tests/createNewRoute.impl.test.js +11 -3
  55. package/dist/tests/createNewRoute.impl.test.js.map +1 -1
  56. package/dist/types/public.d.ts +0 -1
  57. package/dist/types/public.d.ts.map +1 -1
  58. package/dist/types/public.js.map +1 -1
  59. package/package.json +1 -1
  60. package/dist/types/index/conventions.types.d.ts +0 -40
  61. package/dist/types/index/conventions.types.d.ts.map +0 -1
  62. package/dist/types/index/conventions.types.js +0 -2
  63. package/dist/types/index/conventions.types.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"readFile.impl.js","sourceRoot":"","sources":["../../../../src/ai/tools/implementations/readFile.impl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,0BAA0B,EAAsB,MAAM,oBAAoB,CAAC;AAEpF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAmB,EAAE,EAAE;IACxD,MAAM,EAAE,aAAa,EAAE,EAAE,EAAE,GAAG,IAAI,CAAC;IAEnC,OAAO,KAAK,EAAE,QAAgB,EAAE,SAAkB,EAAE,OAAgB,EAAE,EAAE;QACtE,MAAM,QAAQ,GAAG,eAAe,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;QAC1D,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QAElD,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC1B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,GAAI,GAAoC,EAAE,IAAI,CAAC;YACzD,IAAI,IAAI,KAAK,QAAQ;gBAAE,OAAO,0BAA0B,CAAC;YACzD,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;YAC9D,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC5C,OAAO,YAAY,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACnD,CAAC,CAAC;AACJ,CAAC,CAAC","sourcesContent":["import { toWorkspacePath } from \"../helpers/fileSystem.helpers.js\";\nimport { sliceByLines } from \"../helpers/format.helpers.js\";\nimport { DEFAULT_NOT_FOUND_RESPONSE, type WorkspaceDeps } from \"./workspaceDeps.js\";\n\nexport const createReadFileImpl = (deps: WorkspaceDeps) => {\n const { workspaceRoot, fs } = deps;\n\n return async (filePath: string, startLine?: number, endLine?: number) => {\n const fullPath = toWorkspacePath(workspaceRoot, filePath);\n console.log(\"Tool read_file\", { path: fullPath });\n\n try {\n await fs.stat(fullPath);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException | null)?.code;\n if (code === \"ENOENT\") return DEFAULT_NOT_FOUND_RESPONSE;\n console.log(\"Tool read_file failed\", err, { path: fullPath });\n throw err;\n }\n\n const content = await fs.readFile(fullPath);\n return sliceByLines(content, startLine, endLine);\n };\n};\n"]}
1
+ {"version":3,"file":"readFile.impl.js","sourceRoot":"","sources":["../../../../src/ai/tools/implementations/readFile.impl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,0BAA0B,EAAsB,MAAM,oBAAoB,CAAC;AAEpF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAmB,EAAE,EAAE;IACxD,MAAM,EAAE,aAAa,EAAE,EAAE,EAAE,GAAG,IAAI,CAAC;IAEnC,OAAO,KAAK,EAAE,QAAgB,EAAE,SAAkB,EAAE,OAAgB,EAAE,EAAE;QACtE,MAAM,QAAQ,GAAG,eAAe,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;QAC1D,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QAElD,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC1B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,GAAI,GAAoC,EAAE,IAAI,CAAC;YACzD,IAAI,IAAI,KAAK,QAAQ;gBAAE,OAAO,0BAA0B,CAAC;YACzD,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;YAC9D,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAE5C,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC/D,MAAM,QAAQ,GAAG,SAAS,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,CAAC;QAClE,IAAI,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;YACxB,IAAI,CAAC;gBACH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAY,EAAE,CAAC;YAChE,CAAC;YAAC,MAAM,CAAC;gBACP,kFAAkF;YACpF,CAAC;QACH,CAAC;QAED,OAAO,YAAY,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACnD,CAAC,CAAC;AACJ,CAAC,CAAC","sourcesContent":["import { toWorkspacePath } from \"../helpers/fileSystem.helpers.js\";\nimport { sliceByLines } from \"../helpers/format.helpers.js\";\nimport { DEFAULT_NOT_FOUND_RESPONSE, type WorkspaceDeps } from \"./workspaceDeps.js\";\n\nexport const createReadFileImpl = (deps: WorkspaceDeps) => {\n const { workspaceRoot, fs } = deps;\n\n return async (filePath: string, startLine?: number, endLine?: number) => {\n const fullPath = toWorkspacePath(workspaceRoot, filePath);\n console.log(\"Tool read_file\", { path: fullPath });\n\n try {\n await fs.stat(fullPath);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException | null)?.code;\n if (code === \"ENOENT\") return DEFAULT_NOT_FOUND_RESPONSE;\n console.log(\"Tool read_file failed\", err, { path: fullPath });\n throw err;\n }\n\n const content = await fs.readFile(fullPath);\n\n const isJson = filePath.trim().toLowerCase().endsWith(\".json\");\n const isRanged = startLine !== undefined || endLine !== undefined;\n if (isJson && !isRanged) {\n try {\n return { kind: \"json\", json: JSON.parse(content) as unknown };\n } catch {\n // If JSON parsing fails (or file isn't actually JSON), fall back to text slicing.\n }\n }\n\n return sliceByLines(content, startLine, endLine);\n };\n};\n"]}
@@ -7,7 +7,7 @@ export const CreateNewRouteSchema = {
7
7
  properties: {
8
8
  parent_route: {
9
9
  type: Type.STRING,
10
- description: 'The parent route ("/" for app root). Example: "/" or "/dashboard".',
10
+ description: 'The parent route ("/" for app root). Example: "/" or "/dashboard". NEVER pass empty string, use "/" if not sure about parent route.',
11
11
  },
12
12
  route_name: {
13
13
  type: Type.STRING,
@@ -1 +1 @@
1
- {"version":3,"file":"createNewRoute.schema.js","sourceRoot":"","sources":["../../../../src/ai/tools/schemas/createNewRoute.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,IAAI,EAAE,kBAAkB;IACxB,WAAW,EACT,2HAA2H;IAC7H,UAAU,EAAE;QACV,IAAI,EAAE,IAAI,CAAC,MAAM;QACjB,UAAU,EAAE;YACV,YAAY,EAAE;gBACZ,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EAAE,oEAAoE;aAClF;YACD,UAAU,EAAE;gBACV,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EACT,yFAAyF;aAC5F;SACF;QACD,QAAQ,EAAE,CAAC,cAAc,EAAE,YAAY,CAAC;KACzC;CACF,CAAC","sourcesContent":["import { Type } from \"@google/genai\";\n\nexport const CreateNewRouteSchema = {\n name: \"create_new_route\",\n description:\n \"Creates a new Next.js App Router route folder under /app/<parent_route>/<route_name> with a page.tsx and pageConfig.json.\",\n parameters: {\n type: Type.OBJECT,\n properties: {\n parent_route: {\n type: Type.STRING,\n description: 'The parent route (\"/\" for app root). Example: \"/\" or \"/dashboard\".',\n },\n route_name: {\n type: Type.STRING,\n description:\n 'The new route segment to create under the parent route. Example: \"about\" or \"settings\".',\n },\n },\n required: [\"parent_route\", \"route_name\"],\n },\n};\n"]}
1
+ {"version":3,"file":"createNewRoute.schema.js","sourceRoot":"","sources":["../../../../src/ai/tools/schemas/createNewRoute.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,IAAI,EAAE,kBAAkB;IACxB,WAAW,EACT,2HAA2H;IAC7H,UAAU,EAAE;QACV,IAAI,EAAE,IAAI,CAAC,MAAM;QACjB,UAAU,EAAE;YACV,YAAY,EAAE;gBACZ,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EAAE,qIAAqI;aACnJ;YACD,UAAU,EAAE;gBACV,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EACT,yFAAyF;aAC5F;SACF;QACD,QAAQ,EAAE,CAAC,cAAc,EAAE,YAAY,CAAC;KACzC;CACF,CAAC","sourcesContent":["import { Type } from \"@google/genai\";\n\nexport const CreateNewRouteSchema = {\n name: \"create_new_route\",\n description:\n \"Creates a new Next.js App Router route folder under /app/<parent_route>/<route_name> with a page.tsx and pageConfig.json.\",\n parameters: {\n type: Type.OBJECT,\n properties: {\n parent_route: {\n type: Type.STRING,\n description: 'The parent route (\"/\" for app root). Example: \"/\" or \"/dashboard\". NEVER pass empty string, use \"/\" if not sure about parent route.',\n },\n route_name: {\n type: Type.STRING,\n description:\n 'The new route segment to create under the parent route. Example: \"about\" or \"settings\".',\n },\n },\n required: [\"parent_route\", \"route_name\"],\n },\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"insertElement.schema.d.ts","sourceRoot":"","sources":["../../../../src/ai/tools/schemas/insertElement.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,eAAO,MAAM,aAAa,8FAUhB,CAAC;AA2HX,eAAO,MAAM,oBAAoB,EAAE,GAAkC,CAAC;AAEtE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;CAuB/B,CAAC"}
1
+ {"version":3,"file":"insertElement.schema.d.ts","sourceRoot":"","sources":["../../../../src/ai/tools/schemas/insertElement.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,eAAO,MAAM,aAAa,8FAUhB,CAAC;AA0HX,eAAO,MAAM,oBAAoB,EAAE,GAAkC,CAAC;AAEtE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;CAuB/B,CAAC"}
@@ -91,32 +91,32 @@ const buildBuilderElementSchema = (depth) => {
91
91
  type: {
92
92
  type: Type.STRING,
93
93
  enum: ELEMENT_TYPES,
94
- description: "Element type to render. 'text' renders a <p>, 'image' renders an <img>, 'link' renders an <a>, 'icon' renders a Lucide icon, 'fragment' renders children only.",
94
+ description: "Element type to render. Use 'text' for <p>, 'image' forn <img>, 'link' for <a>, 'icon' for Lucide icon, 'fragment' renders children only",
95
95
  },
96
96
  className: {
97
97
  type: Type.STRING,
98
- description: "Tailwind CSS className applied to the rendered element (Tailwind only).",
98
+ description: "Tailwind CSS className (Tailwind only)",
99
99
  },
100
100
  visible: {
101
101
  type: Type.BOOLEAN,
102
- description: "Whether the element should be shown.",
102
+ description: "Visibility flag",
103
103
  },
104
104
  props: BuilderElementPropsSchema,
105
105
  children: {
106
106
  type: Type.ARRAY,
107
- description: "Nested children. Each child is another element and can itself have children (children[].children[]...).",
107
+ description: "Child elements. Each child can itself have children (children[].children[]...)",
108
108
  items: depth > 0
109
109
  ? buildBuilderElementSchema(depth - 1)
110
110
  : {
111
111
  type: Type.OBJECT,
112
- description: "Max depth reached in tool schema. If you need deeper nesting, insert the parent first, then insert children using the returned inserted_id as parent_id.",
112
+ description: "Max depth reached. Insert deeper children separately using the returned inserted_id as parent_id",
113
113
  },
114
114
  },
115
115
  },
116
116
  required: ["type"],
117
117
  };
118
118
  };
119
- export const BuilderElementSchema = buildBuilderElementSchema(6);
119
+ export const BuilderElementSchema = buildBuilderElementSchema(4);
120
120
  export const InsertElementSchema = {
121
121
  name: "insert_element",
122
122
  description: "Inserts element code. Use element.children to create nested UI. Each child is another BuilderElement and can itself have children.",
@@ -125,7 +125,7 @@ export const InsertElementSchema = {
125
125
  properties: {
126
126
  route: {
127
127
  type: Type.STRING,
128
- description: "The route to insert the element at. Example. '/' or '/about' etc.",
128
+ description: "The route to insert the element at. Example. '/' or '/about' etc. Use '/' for the landing page. Never send empty string. ",
129
129
  },
130
130
  parent_id: {
131
131
  type: Type.STRING,
@@ -1 +1 @@
1
- {"version":3,"file":"insertElement.schema.js","sourceRoot":"","sources":["../../../../src/ai/tools/schemas/insertElement.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,UAAU;IACV,KAAK;IACL,MAAM;IACN,OAAO;IACP,QAAQ;IACR,OAAO;IACP,UAAU;IACV,MAAM;IACN,MAAM;CACE,CAAC;AAEX,MAAM,mBAAmB,GAAG;IAC1B,IAAI,EAAE,IAAI,CAAC,MAAM;IACjB,UAAU,EAAE;QACV,IAAI,EAAE;YACJ,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC;YAC7C,WAAW,EACT,2IAA2I;SAC9I;QACD,IAAI,EAAE;YACJ,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,WAAW,EACT,iEAAiE;SACpE;QACD,OAAO,EAAE;YACP,IAAI,EAAE,IAAI,CAAC,OAAO;YAClB,WAAW,EAAE,uDAAuD;SACrE;QACD,MAAM,EAAE;YACN,IAAI,EAAE,IAAI,CAAC,OAAO;YAClB,WAAW,EAAE,8CAA8C;SAC5D;KACF;IACD,QAAQ,EAAE,CAAC,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,yBAAyB,GAAG;IAChC,IAAI,EAAE,IAAI,CAAC,MAAM;IACjB,UAAU,EAAE;QACV,OAAO,EAAE,mBAAmB;QAC5B,IAAI,EAAE;YACJ,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,WAAW,EACT,4GAA4G;SAC/G;QACD,IAAI,EAAE;YACJ,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,WAAW,EAAE,mDAAmD;SACjE;QACD,WAAW,EAAE;YACX,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,WAAW,EAAE,2DAA2D;SACzE;QACD,GAAG,EAAE;YACH,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,WAAW,EACT,gIAAgI;SACnI;QACD,MAAM,EAAE;YACN,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,WAAW,EAAE,+CAA+C;SAC7D;QACD,GAAG,EAAE;YACH,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,WAAW,EAAE,gDAAgD;SAC9D;QACD,KAAK,EAAE;YACL,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,WAAW,EACT,mEAAmE;SACtE;QACD,IAAI,EAAE;YACJ,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,WAAW,EACT,iFAAiF;SACpF;QAED,OAAO;QACP,IAAI,EAAE;YACJ,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,WAAW,EAAE,2DAA2D;SACzE;QACD,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,yBAAyB,EAAE;QACnE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,2BAA2B,EAAE;QACtE,WAAW,EAAE;YACX,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,WAAW,EAAE,2BAA2B;SACzC;KACF;CACF,CAAC;AAEF,gGAAgG;AAChG,2FAA2F;AAC3F,MAAM,yBAAyB,GAAG,CAAC,KAAa,EAAO,EAAE;IACvD,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,MAAM;QACjB,UAAU,EAAE;YACV,IAAI,EAAE;gBACJ,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,IAAI,EAAE,aAAa;gBACnB,WAAW,EACT,gKAAgK;aACnK;YACD,SAAS,EAAE;gBACT,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EACT,yEAAyE;aAC5E;YACD,OAAO,EAAE;gBACP,IAAI,EAAE,IAAI,CAAC,OAAO;gBAClB,WAAW,EAAE,sCAAsC;aACpD;YACD,KAAK,EAAE,yBAAyB;YAChC,QAAQ,EAAE;gBACR,IAAI,EAAE,IAAI,CAAC,KAAK;gBAChB,WAAW,EACT,yGAAyG;gBAC3G,KAAK,EACH,KAAK,GAAG,CAAC;oBACP,CAAC,CAAC,yBAAyB,CAAC,KAAK,GAAG,CAAC,CAAC;oBACtC,CAAC,CAAC;wBACE,IAAI,EAAE,IAAI,CAAC,MAAM;wBACjB,WAAW,EACT,0JAA0J;qBAC7J;aACR;SACF;QACD,QAAQ,EAAE,CAAC,MAAM,CAAC;KACnB,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAQ,yBAAyB,CAAC,CAAC,CAAC,CAAC;AAEtE,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,IAAI,EAAE,gBAAgB;IACtB,WAAW,EACT,oIAAoI;IACtI,UAAU,EAAE;QACV,IAAI,EAAE,IAAI,CAAC,MAAM;QACjB,UAAU,EAAE;YACV,KAAK,EAAE;gBACL,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EACT,mEAAmE;aACtE;YACD,SAAS,EAAE;gBACT,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EAAE,yCAAyC;aACvD;YACD,OAAO,EAAE;gBACP,GAAG,oBAAoB;gBACvB,WAAW,EAAE,wBAAwB;aACtC;SACF;QACD,QAAQ,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,SAAS,CAAC;KAC5C;CACF,CAAC","sourcesContent":["import { Type } from \"@google/genai\";\r\n\r\nexport const ELEMENT_TYPES = [\r\n \"fragment\",\r\n \"div\",\r\n \"text\",\r\n \"image\",\r\n \"button\",\r\n \"input\",\r\n \"textarea\",\r\n \"link\",\r\n \"icon\",\r\n] as const;\r\n\r\nconst OnClickActionSchema = {\r\n type: Type.OBJECT,\r\n properties: {\r\n kind: {\r\n type: Type.STRING,\r\n enum: [\"route\", \"back\", \"reload\", \"external\"],\r\n description:\r\n \"What happens when the element is clicked. 'route' navigates within the app, 'external' opens a URL, 'back' goes back, 'reload' refreshes.\",\r\n },\r\n href: {\r\n type: Type.STRING,\r\n description:\r\n \"URL to navigate to (used for kind='route' and kind='external').\",\r\n },\r\n replace: {\r\n type: Type.BOOLEAN,\r\n description: \"For kind='route': replace history instead of pushing.\",\r\n },\r\n newTab: {\r\n type: Type.BOOLEAN,\r\n description: \"For kind='external': open link in a new tab.\",\r\n },\r\n },\r\n required: [\"kind\"],\r\n};\r\n\r\nconst BuilderElementPropsSchema = {\r\n type: Type.OBJECT,\r\n properties: {\r\n onClick: OnClickActionSchema,\r\n text: {\r\n type: Type.STRING,\r\n description:\r\n \"Text content used by 'text' (<p>), 'button' (label), and as a fallback for 'link' when it has no children.\",\r\n },\r\n href: {\r\n type: Type.STRING,\r\n description: \"For 'link': the href attribute (defaults to '#').\",\r\n },\r\n placeholder: {\r\n type: Type.STRING,\r\n description: \"For 'input' and 'textarea': placeholder shown when empty.\",\r\n },\r\n alt: {\r\n type: Type.STRING,\r\n description:\r\n \"For 'image': alt text for accessibility AND the query used to fetch a suitable Unsplash image (src is auto-resolved from alt).\",\r\n },\r\n target: {\r\n type: Type.STRING,\r\n description: \"For 'link': target attribute (e.g. '_blank').\",\r\n },\r\n rel: {\r\n type: Type.STRING,\r\n description: \"For 'link': rel attribute (e.g. 'noreferrer').\",\r\n },\r\n value: {\r\n type: Type.STRING,\r\n description:\r\n \"For 'input' and 'textarea': default value (maps to defaultValue).\",\r\n },\r\n type: {\r\n type: Type.STRING,\r\n description:\r\n \"For 'input': input type (e.g. 'text', 'email', 'password'). Defaults to 'text'.\",\r\n },\r\n\r\n // icon\r\n name: {\r\n type: Type.STRING,\r\n description: \"For 'icon': Lucide icon name (e.g. 'ArrowRight', 'Menu').\",\r\n },\r\n size: { type: Type.NUMBER, description: \"For 'icon': size in px.\" },\r\n color: { type: Type.STRING, description: \"For 'icon': stroke color.\" },\r\n strokeWidth: {\r\n type: Type.NUMBER,\r\n description: \"For 'icon': stroke width.\",\r\n },\r\n },\r\n};\r\n\r\n// NOTE: Gemini tool `parameters` schemas reject `$ref`, so true recursion isn't available here.\r\n// We unroll nesting to a reasonable max depth; for deeper trees, insert in multiple steps.\r\nconst buildBuilderElementSchema = (depth: number): any => {\r\n return {\r\n type: Type.OBJECT,\r\n properties: {\r\n type: {\r\n type: Type.STRING,\r\n enum: ELEMENT_TYPES,\r\n description:\r\n \"Element type to render. 'text' renders a <p>, 'image' renders an <img>, 'link' renders an <a>, 'icon' renders a Lucide icon, 'fragment' renders children only.\",\r\n },\r\n className: {\r\n type: Type.STRING,\r\n description:\r\n \"Tailwind CSS className applied to the rendered element (Tailwind only).\",\r\n },\r\n visible: {\r\n type: Type.BOOLEAN,\r\n description: \"Whether the element should be shown.\",\r\n },\r\n props: BuilderElementPropsSchema,\r\n children: {\r\n type: Type.ARRAY,\r\n description:\r\n \"Nested children. Each child is another element and can itself have children (children[].children[]...).\",\r\n items:\r\n depth > 0\r\n ? buildBuilderElementSchema(depth - 1)\r\n : {\r\n type: Type.OBJECT,\r\n description:\r\n \"Max depth reached in tool schema. If you need deeper nesting, insert the parent first, then insert children using the returned inserted_id as parent_id.\",\r\n },\r\n },\r\n },\r\n required: [\"type\"],\r\n };\r\n};\r\n\r\nexport const BuilderElementSchema: any = buildBuilderElementSchema(6);\r\n\r\nexport const InsertElementSchema = {\r\n name: \"insert_element\",\r\n description:\r\n \"Inserts element code. Use element.children to create nested UI. Each child is another BuilderElement and can itself have children.\",\r\n parameters: {\r\n type: Type.OBJECT,\r\n properties: {\r\n route: {\r\n type: Type.STRING,\r\n description:\r\n \"The route to insert the element at. Example. '/' or '/about' etc.\",\r\n },\r\n parent_id: {\r\n type: Type.STRING,\r\n description: \"The parent id to insert the element at.\",\r\n },\r\n element: {\r\n ...BuilderElementSchema,\r\n description: \"The element to insert.\",\r\n },\r\n },\r\n required: [\"route\", \"parent_id\", \"element\"],\r\n },\r\n};\r\n"]}
1
+ {"version":3,"file":"insertElement.schema.js","sourceRoot":"","sources":["../../../../src/ai/tools/schemas/insertElement.schema.ts"],"names":[],"mappings":"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","sourcesContent":["import { Type } from \"@google/genai\";\r\n\r\nexport const ELEMENT_TYPES = [\r\n \"fragment\",\r\n \"div\",\r\n \"text\",\r\n \"image\",\r\n \"button\",\r\n \"input\",\r\n \"textarea\",\r\n \"link\",\r\n \"icon\",\r\n] as const;\r\n\r\nconst OnClickActionSchema = {\r\n type: Type.OBJECT,\r\n properties: {\r\n kind: {\r\n type: Type.STRING,\r\n enum: [\"route\", \"back\", \"reload\", \"external\"],\r\n description:\r\n \"What happens when the element is clicked. 'route' navigates within the app, 'external' opens a URL, 'back' goes back, 'reload' refreshes.\",\r\n },\r\n href: {\r\n type: Type.STRING,\r\n description:\r\n \"URL to navigate to (used for kind='route' and kind='external').\",\r\n },\r\n replace: {\r\n type: Type.BOOLEAN,\r\n description: \"For kind='route': replace history instead of pushing.\",\r\n },\r\n newTab: {\r\n type: Type.BOOLEAN,\r\n description: \"For kind='external': open link in a new tab.\",\r\n },\r\n },\r\n required: [\"kind\"],\r\n};\r\n\r\nconst BuilderElementPropsSchema = {\r\n type: Type.OBJECT,\r\n properties: {\r\n onClick: OnClickActionSchema,\r\n text: {\r\n type: Type.STRING,\r\n description:\r\n \"Text content used by 'text' (<p>), 'button' (label), and as a fallback for 'link' when it has no children.\",\r\n },\r\n href: {\r\n type: Type.STRING,\r\n description: \"For 'link': the href attribute (defaults to '#').\",\r\n },\r\n placeholder: {\r\n type: Type.STRING,\r\n description: \"For 'input' and 'textarea': placeholder shown when empty.\",\r\n },\r\n alt: {\r\n type: Type.STRING,\r\n description:\r\n \"For 'image': alt text for accessibility AND the query used to fetch a suitable Unsplash image (src is auto-resolved from alt).\",\r\n },\r\n target: {\r\n type: Type.STRING,\r\n description: \"For 'link': target attribute (e.g. '_blank').\",\r\n },\r\n rel: {\r\n type: Type.STRING,\r\n description: \"For 'link': rel attribute (e.g. 'noreferrer').\",\r\n },\r\n value: {\r\n type: Type.STRING,\r\n description:\r\n \"For 'input' and 'textarea': default value (maps to defaultValue).\",\r\n },\r\n type: {\r\n type: Type.STRING,\r\n description:\r\n \"For 'input': input type (e.g. 'text', 'email', 'password'). Defaults to 'text'.\",\r\n },\r\n\r\n // icon\r\n name: {\r\n type: Type.STRING,\r\n description: \"For 'icon': Lucide icon name (e.g. 'ArrowRight', 'Menu').\",\r\n },\r\n size: { type: Type.NUMBER, description: \"For 'icon': size in px.\" },\r\n color: { type: Type.STRING, description: \"For 'icon': stroke color.\" },\r\n strokeWidth: {\r\n type: Type.NUMBER,\r\n description: \"For 'icon': stroke width.\",\r\n },\r\n },\r\n};\r\n\r\n// NOTE: Gemini tool `parameters` schemas reject `$ref`, so true recursion isn't available here.\r\n// We unroll nesting to a reasonable max depth; for deeper trees, insert in multiple steps.\r\nconst buildBuilderElementSchema = (depth: number): any => {\r\n return {\r\n type: Type.OBJECT,\r\n properties: {\r\n type: {\r\n type: Type.STRING,\r\n enum: ELEMENT_TYPES,\r\n description:\r\n \"Element type to render. Use 'text' for <p>, 'image' forn <img>, 'link' for <a>, 'icon' for Lucide icon, 'fragment' renders children only\",\r\n },\r\n className: {\r\n type: Type.STRING,\r\n description: \"Tailwind CSS className (Tailwind only)\",\r\n },\r\n visible: {\r\n type: Type.BOOLEAN,\r\n description: \"Visibility flag\",\r\n },\r\n props: BuilderElementPropsSchema,\r\n children: {\r\n type: Type.ARRAY,\r\n description:\r\n \"Child elements. Each child can itself have children (children[].children[]...)\",\r\n items:\r\n depth > 0\r\n ? buildBuilderElementSchema(depth - 1)\r\n : {\r\n type: Type.OBJECT,\r\n description:\r\n \"Max depth reached. Insert deeper children separately using the returned inserted_id as parent_id\",\r\n },\r\n },\r\n },\r\n required: [\"type\"],\r\n };\r\n};\r\n\r\nexport const BuilderElementSchema: any = buildBuilderElementSchema(4);\r\n\r\nexport const InsertElementSchema = {\r\n name: \"insert_element\",\r\n description:\r\n \"Inserts element code. Use element.children to create nested UI. Each child is another BuilderElement and can itself have children.\",\r\n parameters: {\r\n type: Type.OBJECT,\r\n properties: {\r\n route: {\r\n type: Type.STRING,\r\n description:\r\n \"The route to insert the element at. Example. '/' or '/about' etc. Use '/' for the landing page. Never send empty string. \",\r\n },\r\n parent_id: {\r\n type: Type.STRING,\r\n description: \"The parent id to insert the element at.\",\r\n },\r\n element: {\r\n ...BuilderElementSchema,\r\n description: \"The element to insert.\",\r\n },\r\n },\r\n required: [\"route\", \"parent_id\", \"element\"],\r\n },\r\n};\r\n"]}
@@ -23,7 +23,7 @@ const OnClickActionSchema = {
23
23
  required: ["kind"],
24
24
  };
25
25
  export const UpdatePropsSchema = {
26
- name: "update_element",
26
+ name: "update_props",
27
27
  description: "Updates element code.",
28
28
  parameters: {
29
29
  type: Type.OBJECT,
@@ -1 +1 @@
1
- {"version":3,"file":"updateProps.schema.js","sourceRoot":"","sources":["../../../../src/ai/tools/schemas/updateProps.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,MAAM,mBAAmB,GAAG;IAC1B,IAAI,EAAE,IAAI,CAAC,MAAM;IACjB,UAAU,EAAE;QACV,IAAI,EAAE;YACJ,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC;YAC7C,WAAW,EACT,2IAA2I;SAC9I;QACD,IAAI,EAAE;YACJ,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,WAAW,EACT,iEAAiE;SACpE;QACD,OAAO,EAAE;YACP,IAAI,EAAE,IAAI,CAAC,OAAO;YAClB,WAAW,EAAE,uDAAuD;SACrE;QACD,MAAM,EAAE;YACN,IAAI,EAAE,IAAI,CAAC,OAAO;YAClB,WAAW,EAAE,8CAA8C;SAC5D;KACF;IACD,QAAQ,EAAE,CAAC,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,IAAI,EAAE,gBAAgB;IACtB,WAAW,EAAE,uBAAuB;IACpC,UAAU,EAAE;QACV,IAAI,EAAE,IAAI,CAAC,MAAM;QACjB,UAAU,EAAE;YACV,KAAK,EAAE;gBACL,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EACT,mEAAmE;aACtE;YACD,UAAU,EAAE;gBACV,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EAAE,sCAAsC;aACpD;YACD,OAAO,EAAE,mBAAmB;YAC5B,IAAI,EAAE;gBACJ,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EACT,4GAA4G;aAC/G;YACD,IAAI,EAAE;gBACJ,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EAAE,mDAAmD;aACjE;YACD,WAAW,EAAE;gBACX,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EAAE,2DAA2D;aACzE;YACD,GAAG,EAAE;gBACH,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EACT,gIAAgI;aACnI;YACD,MAAM,EAAE;gBACN,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EAAE,+CAA+C;aAC7D;YACD,GAAG,EAAE;gBACH,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EAAE,gDAAgD;aAC9D;YACD,KAAK,EAAE;gBACL,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EACT,mEAAmE;aACtE;YACD,IAAI,EAAE;gBACJ,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EACT,iFAAiF;aACpF;YAED,OAAO;YACP,IAAI,EAAE;gBACJ,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EAAE,2DAA2D;aACzE;YACD,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,yBAAyB,EAAE;YACnE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,2BAA2B,EAAE;YACtE,WAAW,EAAE;gBACX,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EAAE,2BAA2B;aACzC;SACF;QACD,QAAQ,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC;KAClC;CACF,CAAC","sourcesContent":["import { Type } from \"@google/genai\";\n\nconst OnClickActionSchema = {\n type: Type.OBJECT,\n properties: {\n kind: {\n type: Type.STRING,\n enum: [\"route\", \"back\", \"reload\", \"external\"],\n description:\n \"What happens when the element is clicked. 'route' navigates within the app, 'external' opens a URL, 'back' goes back, 'reload' refreshes.\",\n },\n href: {\n type: Type.STRING,\n description:\n \"URL to navigate to (used for kind='route' and kind='external').\",\n },\n replace: {\n type: Type.BOOLEAN,\n description: \"For kind='route': replace history instead of pushing.\",\n },\n newTab: {\n type: Type.BOOLEAN,\n description: \"For kind='external': open link in a new tab.\",\n },\n },\n required: [\"kind\"],\n};\n\nexport const UpdatePropsSchema = {\n name: \"update_element\",\n description: \"Updates element code.\",\n parameters: {\n type: Type.OBJECT,\n properties: {\n route: {\n type: Type.STRING,\n description:\n \"The route to update the element at. Example. '/' or '/about' etc.\",\n },\n element_id: {\n type: Type.STRING,\n description: \"The id of the element to be updated.\",\n },\n onClick: OnClickActionSchema,\n text: {\n type: Type.STRING,\n description:\n \"Text content used by 'text' (<p>), 'button' (label), and as a fallback for 'link' when it has no children.\",\n },\n href: {\n type: Type.STRING,\n description: \"For 'link': the href attribute (defaults to '#').\",\n },\n placeholder: {\n type: Type.STRING,\n description: \"For 'input' and 'textarea': placeholder shown when empty.\",\n },\n alt: {\n type: Type.STRING,\n description:\n \"For 'image': alt text for accessibility AND the query used to fetch a suitable Unsplash image (src is auto-resolved from alt).\",\n },\n target: {\n type: Type.STRING,\n description: \"For 'link': target attribute (e.g. '_blank').\",\n },\n rel: {\n type: Type.STRING,\n description: \"For 'link': rel attribute (e.g. 'noreferrer').\",\n },\n value: {\n type: Type.STRING,\n description:\n \"For 'input' and 'textarea': default value (maps to defaultValue).\",\n },\n type: {\n type: Type.STRING,\n description:\n \"For 'input': input type (e.g. 'text', 'email', 'password'). Defaults to 'text'.\",\n },\n\n // icon\n name: {\n type: Type.STRING,\n description: \"For 'icon': Lucide icon name (e.g. 'ArrowRight', 'Menu').\",\n },\n size: { type: Type.NUMBER, description: \"For 'icon': size in px.\" },\n color: { type: Type.STRING, description: \"For 'icon': stroke color.\" },\n strokeWidth: {\n type: Type.NUMBER,\n description: \"For 'icon': stroke width.\",\n },\n },\n required: [\"route\", \"element_id\"],\n },\n};\n"]}
1
+ {"version":3,"file":"updateProps.schema.js","sourceRoot":"","sources":["../../../../src/ai/tools/schemas/updateProps.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,MAAM,mBAAmB,GAAG;IAC1B,IAAI,EAAE,IAAI,CAAC,MAAM;IACjB,UAAU,EAAE;QACV,IAAI,EAAE;YACJ,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC;YAC7C,WAAW,EACT,2IAA2I;SAC9I;QACD,IAAI,EAAE;YACJ,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,WAAW,EACT,iEAAiE;SACpE;QACD,OAAO,EAAE;YACP,IAAI,EAAE,IAAI,CAAC,OAAO;YAClB,WAAW,EAAE,uDAAuD;SACrE;QACD,MAAM,EAAE;YACN,IAAI,EAAE,IAAI,CAAC,OAAO;YAClB,WAAW,EAAE,8CAA8C;SAC5D;KACF;IACD,QAAQ,EAAE,CAAC,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,IAAI,EAAE,cAAc;IACpB,WAAW,EAAE,uBAAuB;IACpC,UAAU,EAAE;QACV,IAAI,EAAE,IAAI,CAAC,MAAM;QACjB,UAAU,EAAE;YACV,KAAK,EAAE;gBACL,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EACT,mEAAmE;aACtE;YACD,UAAU,EAAE;gBACV,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EAAE,sCAAsC;aACpD;YACD,OAAO,EAAE,mBAAmB;YAC5B,IAAI,EAAE;gBACJ,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EACT,4GAA4G;aAC/G;YACD,IAAI,EAAE;gBACJ,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EAAE,mDAAmD;aACjE;YACD,WAAW,EAAE;gBACX,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EAAE,2DAA2D;aACzE;YACD,GAAG,EAAE;gBACH,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EACT,gIAAgI;aACnI;YACD,MAAM,EAAE;gBACN,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EAAE,+CAA+C;aAC7D;YACD,GAAG,EAAE;gBACH,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EAAE,gDAAgD;aAC9D;YACD,KAAK,EAAE;gBACL,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EACT,mEAAmE;aACtE;YACD,IAAI,EAAE;gBACJ,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EACT,iFAAiF;aACpF;YAED,OAAO;YACP,IAAI,EAAE;gBACJ,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EAAE,2DAA2D;aACzE;YACD,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,yBAAyB,EAAE;YACnE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,2BAA2B,EAAE;YACtE,WAAW,EAAE;gBACX,IAAI,EAAE,IAAI,CAAC,MAAM;gBACjB,WAAW,EAAE,2BAA2B;aACzC;SACF;QACD,QAAQ,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC;KAClC;CACF,CAAC","sourcesContent":["import { Type } from \"@google/genai\";\n\nconst OnClickActionSchema = {\n type: Type.OBJECT,\n properties: {\n kind: {\n type: Type.STRING,\n enum: [\"route\", \"back\", \"reload\", \"external\"],\n description:\n \"What happens when the element is clicked. 'route' navigates within the app, 'external' opens a URL, 'back' goes back, 'reload' refreshes.\",\n },\n href: {\n type: Type.STRING,\n description:\n \"URL to navigate to (used for kind='route' and kind='external').\",\n },\n replace: {\n type: Type.BOOLEAN,\n description: \"For kind='route': replace history instead of pushing.\",\n },\n newTab: {\n type: Type.BOOLEAN,\n description: \"For kind='external': open link in a new tab.\",\n },\n },\n required: [\"kind\"],\n};\n\nexport const UpdatePropsSchema = {\n name: \"update_props\",\n description: \"Updates element code.\",\n parameters: {\n type: Type.OBJECT,\n properties: {\n route: {\n type: Type.STRING,\n description:\n \"The route to update the element at. Example. '/' or '/about' etc.\",\n },\n element_id: {\n type: Type.STRING,\n description: \"The id of the element to be updated.\",\n },\n onClick: OnClickActionSchema,\n text: {\n type: Type.STRING,\n description:\n \"Text content used by 'text' (<p>), 'button' (label), and as a fallback for 'link' when it has no children.\",\n },\n href: {\n type: Type.STRING,\n description: \"For 'link': the href attribute (defaults to '#').\",\n },\n placeholder: {\n type: Type.STRING,\n description: \"For 'input' and 'textarea': placeholder shown when empty.\",\n },\n alt: {\n type: Type.STRING,\n description:\n \"For 'image': alt text for accessibility AND the query used to fetch a suitable Unsplash image (src is auto-resolved from alt).\",\n },\n target: {\n type: Type.STRING,\n description: \"For 'link': target attribute (e.g. '_blank').\",\n },\n rel: {\n type: Type.STRING,\n description: \"For 'link': rel attribute (e.g. 'noreferrer').\",\n },\n value: {\n type: Type.STRING,\n description:\n \"For 'input' and 'textarea': default value (maps to defaultValue).\",\n },\n type: {\n type: Type.STRING,\n description:\n \"For 'input': input type (e.g. 'text', 'email', 'password'). Defaults to 'text'.\",\n },\n\n // icon\n name: {\n type: Type.STRING,\n description: \"For 'icon': Lucide icon name (e.g. 'ArrowRight', 'Menu').\",\n },\n size: { type: Type.NUMBER, description: \"For 'icon': size in px.\" },\n color: { type: Type.STRING, description: \"For 'icon': stroke color.\" },\n strokeWidth: {\n type: Type.NUMBER,\n description: \"For 'icon': stroke width.\",\n },\n },\n required: [\"route\", \"element_id\"],\n },\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"unsplash.service.d.ts","sourceRoot":"","sources":["../../src/image/unsplash.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,KAAK,cAAc,GAAG;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAMF,wBAAgB,YAAY,CAC1B,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI,GAAG,SAAS,QAcnD;AAoBD,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CA4E/B;AAED,eAAO,MAAM,8BAA8B,GACzC,IAAI,cAAc,EAClB,KAAK,MAAM,KACV,OAAO,CAAC,IAAI,CAmBd,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,IAAI,cAAc,KACjB,OAAO,CAAC,IAAI,CAcd,CAAC"}
1
+ {"version":3,"file":"unsplash.service.d.ts","sourceRoot":"","sources":["../../src/image/unsplash.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,KAAK,cAAc,GAAG;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAMF,wBAAgB,YAAY,CAC1B,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI,GAAG,SAAS,QAcnD;AAoBD,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CA6E/B;AAED,eAAO,MAAM,8BAA8B,GACzC,IAAI,cAAc,EAClB,KAAK,MAAM,KACV,OAAO,CAAC,IAAI,CAmBd,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,IAAI,cAAc,KACjB,OAAO,CAAC,IAAI,CAcd,CAAC"}
@@ -26,6 +26,7 @@ function scoreImage(photo) {
26
26
  return score;
27
27
  }
28
28
  export async function searchUnsplashImage(imgQuery) {
29
+ console.log("searchUnsplashImage", imgQuery);
29
30
  const cfg = unsplashConfig;
30
31
  if (!cfg)
31
32
  throw new Error("Unsplash not configured");
@@ -1 +1 @@
1
- {"version":3,"file":"unsplash.service.js","sourceRoot":"","sources":["../../src/image/unsplash.service.ts"],"names":[],"mappings":"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","sourcesContent":["import crypto from \"crypto\";\r\nimport { BuilderElement } from \"../types/elements.js\";\r\n\r\nexport interface ResolvedImage {\r\n id: string;\r\n alt: string;\r\n imageUrl: string;\r\n thumbUrl: string;\r\n photographer: string;\r\n photographerUrl: string;\r\n unsplashUrl: string;\r\n width: number;\r\n height: number;\r\n}\r\n\r\ntype UnsplashConfig = {\r\n url: string;\r\n accessKey: string;\r\n};\r\n\r\nlet unsplashConfig: UnsplashConfig | null = null;\r\n\r\nconst cache = new Map<string, ResolvedImage>();\r\n\r\nexport function initUnsplash(\r\n config: Partial<UnsplashConfig> | null | undefined,\r\n) {\r\n const url = String(config?.url ?? \"\").trim();\r\n const accessKey = String(config?.accessKey ?? \"\").trim();\r\n\r\n if (!url || !accessKey) {\r\n unsplashConfig = null;\r\n return;\r\n }\r\n\r\n unsplashConfig = {\r\n url: url.replace(/\\/+$/, \"\"),\r\n accessKey,\r\n };\r\n}\r\n\r\nfunction hashIntent(imgQuery: string) {\r\n return crypto.createHash(\"sha256\").update(imgQuery).digest(\"hex\");\r\n}\r\n\r\nfunction scoreImage(photo: any) {\r\n let score = 0;\r\n\r\n score += (photo.likes || 0) * 0.3;\r\n\r\n if (photo.width > photo.height) {\r\n score += 50;\r\n }\r\n\r\n score += (photo.user.total_photos || 0) * 0.05;\r\n\r\n return score;\r\n}\r\n\r\nexport async function searchUnsplashImage(\r\n imgQuery: string,\r\n): Promise<ResolvedImage | null> {\r\n const cfg = unsplashConfig;\r\n if (!cfg) throw new Error(\"Unsplash not configured\");\r\n\r\n const cacheKey = hashIntent(imgQuery);\r\n\r\n const cached = cache.get(cacheKey);\r\n\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n const params = new URLSearchParams({\r\n imgQuery,\r\n per_page: \"10\",\r\n });\r\n\r\n const response = await fetch(\r\n `${cfg.url}/search/photos?${params.toString()}`,\r\n {\r\n headers: {\r\n Authorization: `Client-ID ${cfg.accessKey}`,\r\n },\r\n },\r\n );\r\n\r\n if (!response.ok) {\r\n throw new Error(`Unsplash API failed: ${response.status}`);\r\n }\r\n\r\n const data = await response.json();\r\n\r\n const photos = data.results;\r\n\r\n if (!photos.length) {\r\n return null;\r\n }\r\n\r\n const ranked = [...photos].sort((a, b) => {\r\n return scoreImage(b) - scoreImage(a);\r\n });\r\n\r\n const top3 = ranked.slice(0, 3);\r\n\r\n const selected = top3[Math.floor(Math.random() * top3.length)];\r\n\r\n // IMPORTANT:\r\n // Required by Unsplash API terms\r\n // Tracks image download usage\r\n if (selected.links.download_location) {\r\n await fetch(selected.links.download_location, {\r\n headers: {\r\n Authorization: `Client-ID ${cfg.accessKey}`,\r\n },\r\n });\r\n }\r\n\r\n const resolved: ResolvedImage = {\r\n id: selected.id,\r\n alt: imgQuery,\r\n\r\n imageUrl: selected.urls.regular,\r\n thumbUrl: selected.urls.small,\r\n\r\n photographer: selected.user.name,\r\n photographerUrl: selected.user.links.html,\r\n\r\n unsplashUrl: selected.links.html,\r\n\r\n width: selected.width,\r\n height: selected.height,\r\n };\r\n\r\n cache.set(cacheKey, resolved);\r\n\r\n return resolved;\r\n}\r\n\r\nexport const resolveUnsplashImageForElement = async (\r\n el: BuilderElement,\r\n alt: string,\r\n): Promise<void> => {\r\n if (!unsplashConfig) return;\r\n const query = String(alt ?? \"\").trim();\r\n if (!query) return;\r\n if (el.props?.src) {\r\n console.log(\"src already exists, skipping\");\r\n return;\r\n }\r\n try {\r\n const resolved = await searchUnsplashImage(query);\r\n if (!resolved?.imageUrl) return;\r\n\r\n const anyEl = el as any;\r\n if (!anyEl.props || typeof anyEl.props !== \"object\") anyEl.props = {};\r\n anyEl.props.src = resolved.imageUrl;\r\n anyEl.props.alt = query;\r\n } catch {\r\n // Ignore Unsplash lookup errors and still allow prop updates.\r\n }\r\n};\r\n\r\nexport const resolveUnsplashImagesDeep = async (\r\n el: BuilderElement,\r\n): Promise<void> => {\r\n const anyEl = el as any;\r\n\r\n if (anyEl?.type === \"image\") {\r\n const alt = String(anyEl?.props?.alt ?? \"\").trim();\r\n resolveUnsplashImageForElement(el, alt);\r\n }\r\n\r\n const kids = anyEl?.children;\r\n if (Array.isArray(kids)) {\r\n for (const child of kids) {\r\n await resolveUnsplashImagesDeep(child);\r\n }\r\n }\r\n};\r\n"]}
1
+ {"version":3,"file":"unsplash.service.js","sourceRoot":"","sources":["../../src/image/unsplash.service.ts"],"names":[],"mappings":"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","sourcesContent":["import crypto from \"crypto\";\r\nimport { BuilderElement } from \"../types/elements.js\";\r\n\r\nexport interface ResolvedImage {\r\n id: string;\r\n alt: string;\r\n imageUrl: string;\r\n thumbUrl: string;\r\n photographer: string;\r\n photographerUrl: string;\r\n unsplashUrl: string;\r\n width: number;\r\n height: number;\r\n}\r\n\r\ntype UnsplashConfig = {\r\n url: string;\r\n accessKey: string;\r\n};\r\n\r\nlet unsplashConfig: UnsplashConfig | null = null;\r\n\r\nconst cache = new Map<string, ResolvedImage>();\r\n\r\nexport function initUnsplash(\r\n config: Partial<UnsplashConfig> | null | undefined,\r\n) {\r\n const url = String(config?.url ?? \"\").trim();\r\n const accessKey = String(config?.accessKey ?? \"\").trim();\r\n\r\n if (!url || !accessKey) {\r\n unsplashConfig = null;\r\n return;\r\n }\r\n\r\n unsplashConfig = {\r\n url: url.replace(/\\/+$/, \"\"),\r\n accessKey,\r\n };\r\n}\r\n\r\nfunction hashIntent(imgQuery: string) {\r\n return crypto.createHash(\"sha256\").update(imgQuery).digest(\"hex\");\r\n}\r\n\r\nfunction scoreImage(photo: any) {\r\n let score = 0;\r\n\r\n score += (photo.likes || 0) * 0.3;\r\n\r\n if (photo.width > photo.height) {\r\n score += 50;\r\n }\r\n\r\n score += (photo.user.total_photos || 0) * 0.05;\r\n\r\n return score;\r\n}\r\n\r\nexport async function searchUnsplashImage(\r\n imgQuery: string,\r\n): Promise<ResolvedImage | null> {\r\n console.log(\"searchUnsplashImage\", imgQuery);\r\n const cfg = unsplashConfig;\r\n if (!cfg) throw new Error(\"Unsplash not configured\");\r\n\r\n const cacheKey = hashIntent(imgQuery);\r\n\r\n const cached = cache.get(cacheKey);\r\n\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n const params = new URLSearchParams({\r\n imgQuery,\r\n per_page: \"10\",\r\n });\r\n\r\n const response = await fetch(\r\n `${cfg.url}/search/photos?${params.toString()}`,\r\n {\r\n headers: {\r\n Authorization: `Client-ID ${cfg.accessKey}`,\r\n },\r\n },\r\n );\r\n\r\n if (!response.ok) {\r\n throw new Error(`Unsplash API failed: ${response.status}`);\r\n }\r\n\r\n const data = await response.json();\r\n\r\n const photos = data.results;\r\n\r\n if (!photos.length) {\r\n return null;\r\n }\r\n\r\n const ranked = [...photos].sort((a, b) => {\r\n return scoreImage(b) - scoreImage(a);\r\n });\r\n\r\n const top3 = ranked.slice(0, 3);\r\n\r\n const selected = top3[Math.floor(Math.random() * top3.length)];\r\n\r\n // IMPORTANT:\r\n // Required by Unsplash API terms\r\n // Tracks image download usage\r\n if (selected.links.download_location) {\r\n await fetch(selected.links.download_location, {\r\n headers: {\r\n Authorization: `Client-ID ${cfg.accessKey}`,\r\n },\r\n });\r\n }\r\n\r\n const resolved: ResolvedImage = {\r\n id: selected.id,\r\n alt: imgQuery,\r\n\r\n imageUrl: selected.urls.regular,\r\n thumbUrl: selected.urls.small,\r\n\r\n photographer: selected.user.name,\r\n photographerUrl: selected.user.links.html,\r\n\r\n unsplashUrl: selected.links.html,\r\n\r\n width: selected.width,\r\n height: selected.height,\r\n };\r\n\r\n cache.set(cacheKey, resolved);\r\n\r\n return resolved;\r\n}\r\n\r\nexport const resolveUnsplashImageForElement = async (\r\n el: BuilderElement,\r\n alt: string,\r\n): Promise<void> => {\r\n if (!unsplashConfig) return;\r\n const query = String(alt ?? \"\").trim();\r\n if (!query) return;\r\n if (el.props?.src) {\r\n console.log(\"src already exists, skipping\");\r\n return;\r\n }\r\n try {\r\n const resolved = await searchUnsplashImage(query);\r\n if (!resolved?.imageUrl) return;\r\n\r\n const anyEl = el as any;\r\n if (!anyEl.props || typeof anyEl.props !== \"object\") anyEl.props = {};\r\n anyEl.props.src = resolved.imageUrl;\r\n anyEl.props.alt = query;\r\n } catch {\r\n // Ignore Unsplash lookup errors and still allow prop updates.\r\n }\r\n};\r\n\r\nexport const resolveUnsplashImagesDeep = async (\r\n el: BuilderElement,\r\n): Promise<void> => {\r\n const anyEl = el as any;\r\n\r\n if (anyEl?.type === \"image\") {\r\n const alt = String(anyEl?.props?.alt ?? \"\").trim();\r\n resolveUnsplashImageForElement(el, alt);\r\n }\r\n\r\n const kids = anyEl?.children;\r\n if (Array.isArray(kids)) {\r\n for (const child of kids) {\r\n await resolveUnsplashImagesDeep(child);\r\n }\r\n }\r\n};\r\n"]}
@@ -20,40 +20,33 @@ export declare const projectConfigs: {
20
20
  readonly testing: "none";
21
21
  };
22
22
  readonly renderingConfig: {
23
- readonly approach: "config-driven UI rendering";
24
- readonly routeConventions: {
25
- readonly model: "Routes mean URL paths (e.g. /about, /pricing) in a Next.js App Router app. Each URL route is implemented by a route folder under /app (e.g. app/about) and is driven by a colocated JSON config file.";
26
- readonly definition: "In this repo, \"route\" always refers to the user-facing browser URL pathname, not an arbitrary file path.";
27
- readonly examples: {
28
- readonly urlRoutes: readonly ["/", "/about", "/pricing"];
29
- readonly appRouterFolders: readonly ["app/page.tsx", "app/about/page.tsx", "app/pricing/page.tsx"];
23
+ readonly approach: "config-driven";
24
+ readonly routes: {
25
+ readonly meaning: "URL pathname, not file path";
26
+ readonly examples: readonly ["/", "/about", "/pricing"];
27
+ readonly structure: {
28
+ readonly "/": "app/page.tsx + app/pageConfig.json";
29
+ readonly "/about": "app/about/page.tsx + app/about/pageConfig.json";
30
30
  };
31
- readonly filesPerRoute: readonly ["page.tsx", "pageConfig.json"];
32
- readonly sourceOfTruth: "pageConfig.json (edit this; page.tsx is a fixed renderer)";
33
- readonly configShape: {
34
- readonly file: "pageConfig.json";
35
- readonly root: {
36
- readonly elements: "BuilderElement[]";
37
- };
38
- readonly note: "The page renders config.elements (array of BuilderElement).";
31
+ readonly files: readonly ["page.tsx", "pageConfig.json"];
32
+ readonly sourceOfTruth: "pageConfig.json";
33
+ readonly config: {
34
+ readonly root: "elements: BuilderElement[]";
39
35
  };
40
- readonly locationNote: "These files live inside the route folder (App Router convention). Example: for /about -> app/about/page.tsx and app/about/pageConfig.json.";
41
36
  };
42
- readonly pageRenderer: {
43
- readonly fileName: "page.tsx";
44
- readonly behavior: readonly ["Imports pageConfig from ./pageConfig.json", "Casts config to { elements: BuilderElement[] }", "Renders: config.elements.map(el => <RenderElement key={el.id} el={el} />)"];
45
- readonly canonicalSource: "import { RenderElement } from \"@/lib/renderer/RenderElement\";\nimport pageConfig from \"./pageConfig.json\";\nimport type { BuilderElement } from \"@/types/elements\";\n\nexport default function Page() {\n const config = pageConfig as { elements: BuilderElement[] };\n return config.elements.map((el) => <RenderElement key={el.id} el={el} />);\n}";
37
+ readonly renderer: {
38
+ readonly file: "page.tsx";
39
+ readonly behavior: readonly ["imports ./pageConfig.json", "casts to { elements: BuilderElement[] }", "renders config.elements with RenderElement"];
40
+ readonly fixed: true;
46
41
  };
47
42
  readonly elements: {
48
- readonly typeName: "BuilderElement";
49
- readonly supportedTypes: readonly ["fragment", "div", "text", "image", "button", "input", "textarea", "link", "icon"];
50
- readonly rendering: {
51
- readonly notes: readonly ["Unknown element types are warned and rendered as an error box.", "Elements support Tailwind-only styling via className.", "Elements may have children: BuilderElement[]."];
52
- };
43
+ readonly type: "BuilderElement";
44
+ readonly supported: readonly ["fragment", "div", "text", "image", "button", "input", "textarea", "link", "icon"];
45
+ readonly rules: readonly ["Tailwind via className only", "supports children: BuilderElement[]", "unknown types render error UI"];
53
46
  };
54
- readonly generatorGuidance: {
55
- readonly do: readonly ["Treat a route as a URL path (e.g. /about). Its code lives in the corresponding Next.js App Router folder (e.g. app/about).", "UI changes can happen only by editing that route folder's pageConfig.json elements tree.", "@/lib/renderer/RenderElement , @/types/elements already exist. No need to create them.", "When creating a new URL route (e.g. /pricing), create the App Router folder (e.g. app/pricing) with page.tsx (fixed renderer) + pageConfig.json (initial elements). After that, codegen should only modify pageConfig.json.", "Use Tailwind in className; prefer composition via nested children."];
56
- readonly dont: readonly ["Do not confuse URL routes with file paths; do not invent 'routes' that are just filenames.", "Do not change page.tsx structure for URL routes (it is a fixed renderer).", "Do not generate React components for layout; encode structure in JSON elements."];
47
+ readonly guidance: {
48
+ readonly do: readonly ["route = URL path", "edit only pageConfig.json for UI changes", "keep '/' populated", "RenderElement and BuilderElement already exist", "new routes require page.tsx + pageConfig.json", "prefer nested composition"];
49
+ readonly dont: readonly ["don't treat routes as file paths", "don't modify page.tsx", "don't generate React layout components"];
57
50
  };
58
51
  };
59
52
  };
@@ -1 +1 @@
1
- {"version":3,"file":"configs.constants.d.ts","sourceRoot":"","sources":["../../../src/indexer/data/configs.constants.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+Fc,CAAC;AAE1C,eAAO,MAAM,QAAQ;;;;;;CAyBc,CAAC"}
1
+ {"version":3,"file":"configs.constants.d.ts","sourceRoot":"","sources":["../../../src/indexer/data/configs.constants.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6Fc,CAAC;AAE1C,eAAO,MAAM,QAAQ;;;;;;CAyBc,CAAC"}
@@ -20,42 +20,32 @@ export const projectConfigs = {
20
20
  testing: "none",
21
21
  },
22
22
  renderingConfig: {
23
- approach: "config-driven UI rendering",
24
- routeConventions: {
25
- model: "Routes mean URL paths (e.g. /about, /pricing) in a Next.js App Router app. Each URL route is implemented by a route folder under /app (e.g. app/about) and is driven by a colocated JSON config file.",
26
- definition: 'In this repo, "route" always refers to the user-facing browser URL pathname, not an arbitrary file path.',
27
- examples: {
28
- urlRoutes: ["/", "/about", "/pricing"],
29
- appRouterFolders: ["app/page.tsx", "app/about/page.tsx", "app/pricing/page.tsx"],
23
+ approach: "config-driven",
24
+ routes: {
25
+ meaning: "URL pathname, not file path",
26
+ examples: ["/", "/about", "/pricing"],
27
+ structure: {
28
+ "/": "app/page.tsx + app/pageConfig.json",
29
+ "/about": "app/about/page.tsx + app/about/pageConfig.json",
30
30
  },
31
- filesPerRoute: ["page.tsx", "pageConfig.json"],
32
- sourceOfTruth: "pageConfig.json (edit this; page.tsx is a fixed renderer)",
33
- configShape: {
34
- file: "pageConfig.json",
35
- root: { elements: "BuilderElement[]" },
36
- note: "The page renders config.elements (array of BuilderElement).",
31
+ files: ["page.tsx", "pageConfig.json"],
32
+ sourceOfTruth: "pageConfig.json",
33
+ config: {
34
+ root: "elements: BuilderElement[]",
37
35
  },
38
- locationNote: "These files live inside the route folder (App Router convention). Example: for /about -> app/about/page.tsx and app/about/pageConfig.json.",
39
36
  },
40
- pageRenderer: {
41
- fileName: "page.tsx",
37
+ renderer: {
38
+ file: "page.tsx",
42
39
  behavior: [
43
- "Imports pageConfig from ./pageConfig.json",
44
- "Casts config to { elements: BuilderElement[] }",
45
- "Renders: config.elements.map(el => <RenderElement key={el.id} el={el} />)",
40
+ "imports ./pageConfig.json",
41
+ "casts to { elements: BuilderElement[] }",
42
+ "renders config.elements with RenderElement",
46
43
  ],
47
- canonicalSource: `import { RenderElement } from "@/lib/renderer/RenderElement";
48
- import pageConfig from "./pageConfig.json";
49
- import type { BuilderElement } from "@/types/elements";
50
-
51
- export default function Page() {
52
- const config = pageConfig as { elements: BuilderElement[] };
53
- return config.elements.map((el) => <RenderElement key={el.id} el={el} />);
54
- }`,
44
+ fixed: true,
55
45
  },
56
46
  elements: {
57
- typeName: "BuilderElement",
58
- supportedTypes: [
47
+ type: "BuilderElement",
48
+ supported: [
59
49
  "fragment",
60
50
  "div",
61
51
  "text",
@@ -66,26 +56,25 @@ export default function Page() {
66
56
  "link",
67
57
  "icon",
68
58
  ],
69
- rendering: {
70
- notes: [
71
- "Unknown element types are warned and rendered as an error box.",
72
- "Elements support Tailwind-only styling via className.",
73
- "Elements may have children: BuilderElement[].",
74
- ],
75
- },
59
+ rules: [
60
+ "Tailwind via className only",
61
+ "supports children: BuilderElement[]",
62
+ "unknown types render error UI",
63
+ ],
76
64
  },
77
- generatorGuidance: {
65
+ guidance: {
78
66
  do: [
79
- "Treat a route as a URL path (e.g. /about). Its code lives in the corresponding Next.js App Router folder (e.g. app/about).",
80
- "UI changes can happen only by editing that route folder's pageConfig.json elements tree.",
81
- "@/lib/renderer/RenderElement , @/types/elements already exist. No need to create them.",
82
- "When creating a new URL route (e.g. /pricing), create the App Router folder (e.g. app/pricing) with page.tsx (fixed renderer) + pageConfig.json (initial elements). After that, codegen should only modify pageConfig.json.",
83
- "Use Tailwind in className; prefer composition via nested children.",
67
+ "route = URL path",
68
+ "edit only pageConfig.json for UI changes",
69
+ "keep '/' populated",
70
+ "RenderElement and BuilderElement already exist",
71
+ "new routes require page.tsx + pageConfig.json",
72
+ "prefer nested composition",
84
73
  ],
85
74
  dont: [
86
- "Do not confuse URL routes with file paths; do not invent 'routes' that are just filenames.",
87
- "Do not change page.tsx structure for URL routes (it is a fixed renderer).",
88
- "Do not generate React components for layout; encode structure in JSON elements.",
75
+ "don't treat routes as file paths",
76
+ "don't modify page.tsx",
77
+ "don't generate React layout components",
89
78
  ],
90
79
  },
91
80
  },
@@ -1 +1 @@
1
- {"version":3,"file":"configs.constants.js","sourceRoot":"","sources":["../../../src/indexer/data/configs.constants.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,eAAe,EAAE;QACf,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,YAAY;QACpB,QAAQ,EAAE,YAAY;QACtB,KAAK,EAAE,oBAAoB;QAC3B,OAAO,EAAE,cAAc;KACxB;IACD,aAAa,EAAE;QACb,MAAM,EAAE,eAAe;QACvB,aAAa,EAAE,UAAU;QACzB,SAAS,EAAE,UAAU;QACrB,YAAY,EAAE,+BAA+B;KAC9C;IACD,aAAa,EAAE;QACb,cAAc,EAAE,KAAK;QACrB,OAAO,EAAE,QAAQ;QACjB,UAAU,EAAE,UAAU;QACtB,SAAS,EAAE,cAAc;QACzB,OAAO,EAAE,MAAM;KAChB;IACD,eAAe,EAAE;QACf,QAAQ,EAAE,4BAA4B;QACtC,gBAAgB,EAAE;YAChB,KAAK,EACH,uMAAuM;YACzM,UAAU,EACR,0GAA0G;YAC5G,QAAQ,EAAE;gBACR,SAAS,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,UAAU,CAAC;gBACtC,gBAAgB,EAAE,CAAC,cAAc,EAAE,oBAAoB,EAAE,sBAAsB,CAAC;aACjF;YACD,aAAa,EAAE,CAAC,UAAU,EAAE,iBAAiB,CAAC;YAC9C,aAAa,EACX,2DAA2D;YAC7D,WAAW,EAAE;gBACX,IAAI,EAAE,iBAAiB;gBACvB,IAAI,EAAE,EAAE,QAAQ,EAAE,kBAAkB,EAAE;gBACtC,IAAI,EAAE,6DAA6D;aACpE;YACD,YAAY,EACV,4IAA4I;SAC/I;QACD,YAAY,EAAE;YACZ,QAAQ,EAAE,UAAU;YACpB,QAAQ,EAAE;gBACR,2CAA2C;gBAC3C,gDAAgD;gBAChD,2EAA2E;aAC5E;YACD,eAAe,EAAE;;;;;;;EAOrB;SACG;QACD,QAAQ,EAAE;YACR,QAAQ,EAAE,gBAAgB;YAC1B,cAAc,EAAE;gBACd,UAAU;gBACV,KAAK;gBACL,MAAM;gBACN,OAAO;gBACP,QAAQ;gBACR,OAAO;gBACP,UAAU;gBACV,MAAM;gBACN,MAAM;aACP;YACD,SAAS,EAAE;gBACT,KAAK,EAAE;oBACL,gEAAgE;oBAChE,uDAAuD;oBACvD,+CAA+C;iBAChD;aACF;SACF;QACD,iBAAiB,EAAE;YACjB,EAAE,EAAE;gBACF,4HAA4H;gBAC5H,0FAA0F;gBAC1F,wFAAwF;gBACxF,6NAA6N;gBAC7N,oEAAoE;aACrE;YACD,IAAI,EAAE;gBACJ,4FAA4F;gBAC5F,2EAA2E;gBAC3E,iFAAiF;aAClF;SACF;KACF;CACsC,CAAC;AAE1C,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,iBAAiB,EAAE;QACjB,KAAK;QACL,MAAM;QACN,KAAK;QACL,MAAM;QACN,OAAO;QACP,KAAK;QACL,MAAM;QACN,MAAM;QACN,OAAO;QACP,OAAO;KACR;IACD,kBAAkB,EAAE;QAClB,cAAc;QACd,OAAO;QACP,MAAM;QACN,OAAO;QACP,KAAK;QACL,UAAU;QACV,MAAM;KACP;IACD,YAAY,EAAE,MAAO;IACrB,SAAS,EAAE,GAAG;IACd,YAAY,EAAE,GAAG;CACgB,CAAC","sourcesContent":["import { ProjectConfigsConfig } from \"../../types/index/configs.types.js\";\nimport { IndexingConfig } from \"../../types/index/indexing.types.js\";\n\nexport const projectConfigs = {\n frameworkConfig: {\n name: \"Next.js\",\n router: \"App Router\",\n language: \"TypeScript\",\n icons: \"lucide-react icons\",\n styling: \"Tailwind CSS\",\n },\n runtimeConfig: {\n target: \"frontend-only\",\n serverActions: \"disabled\",\n apiRoutes: \"disabled\",\n dataFetching: \"client-side (fetch or mocked)\",\n },\n toolingConfig: {\n packageManager: \"npm\",\n linting: \"eslint\",\n formatting: \"prettier\",\n typecheck: \"tsc --noEmit\",\n testing: \"none\",\n },\n renderingConfig: {\n approach: \"config-driven UI rendering\",\n routeConventions: {\n model:\n \"Routes mean URL paths (e.g. /about, /pricing) in a Next.js App Router app. Each URL route is implemented by a route folder under /app (e.g. app/about) and is driven by a colocated JSON config file.\",\n definition:\n 'In this repo, \"route\" always refers to the user-facing browser URL pathname, not an arbitrary file path.',\n examples: {\n urlRoutes: [\"/\", \"/about\", \"/pricing\"],\n appRouterFolders: [\"app/page.tsx\", \"app/about/page.tsx\", \"app/pricing/page.tsx\"],\n },\n filesPerRoute: [\"page.tsx\", \"pageConfig.json\"],\n sourceOfTruth:\n \"pageConfig.json (edit this; page.tsx is a fixed renderer)\",\n configShape: {\n file: \"pageConfig.json\",\n root: { elements: \"BuilderElement[]\" },\n note: \"The page renders config.elements (array of BuilderElement).\",\n },\n locationNote:\n \"These files live inside the route folder (App Router convention). Example: for /about -> app/about/page.tsx and app/about/pageConfig.json.\",\n },\n pageRenderer: {\n fileName: \"page.tsx\",\n behavior: [\n \"Imports pageConfig from ./pageConfig.json\",\n \"Casts config to { elements: BuilderElement[] }\",\n \"Renders: config.elements.map(el => <RenderElement key={el.id} el={el} />)\",\n ],\n canonicalSource: `import { RenderElement } from \"@/lib/renderer/RenderElement\";\nimport pageConfig from \"./pageConfig.json\";\nimport type { BuilderElement } from \"@/types/elements\";\n\nexport default function Page() {\n const config = pageConfig as { elements: BuilderElement[] };\n return config.elements.map((el) => <RenderElement key={el.id} el={el} />);\n}`,\n },\n elements: {\n typeName: \"BuilderElement\",\n supportedTypes: [\n \"fragment\",\n \"div\",\n \"text\",\n \"image\",\n \"button\",\n \"input\",\n \"textarea\",\n \"link\",\n \"icon\",\n ],\n rendering: {\n notes: [\n \"Unknown element types are warned and rendered as an error box.\",\n \"Elements support Tailwind-only styling via className.\",\n \"Elements may have children: BuilderElement[].\",\n ],\n },\n },\n generatorGuidance: {\n do: [\n \"Treat a route as a URL path (e.g. /about). Its code lives in the corresponding Next.js App Router folder (e.g. app/about).\",\n \"UI changes can happen only by editing that route folder's pageConfig.json elements tree.\",\n \"@/lib/renderer/RenderElement , @/types/elements already exist. No need to create them.\",\n \"When creating a new URL route (e.g. /pricing), create the App Router folder (e.g. app/pricing) with page.tsx (fixed renderer) + pageConfig.json (initial elements). After that, codegen should only modify pageConfig.json.\",\n \"Use Tailwind in className; prefer composition via nested children.\",\n ],\n dont: [\n \"Do not confuse URL routes with file paths; do not invent 'routes' that are just filenames.\",\n \"Do not change page.tsx structure for URL routes (it is a fixed renderer).\",\n \"Do not generate React components for layout; encode structure in JSON elements.\",\n ],\n },\n },\n} as const satisfies ProjectConfigsConfig;\n\nexport const indexing = {\n includeExtensions: [\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \".json\",\n \".md\",\n \".mdx\",\n \".css\",\n \".scss\",\n \".sass\",\n ],\n excludeDirectories: [\n \"node_modules\",\n \".next\",\n \"dist\",\n \"build\",\n \"out\",\n \"coverage\",\n \".git\",\n ],\n maxFileBytes: 200_000,\n chunkSize: 900,\n chunkOverlap: 150,\n} as const satisfies IndexingConfig;\n"]}
1
+ {"version":3,"file":"configs.constants.js","sourceRoot":"","sources":["../../../src/indexer/data/configs.constants.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,eAAe,EAAE;QACf,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,YAAY;QACpB,QAAQ,EAAE,YAAY;QACtB,KAAK,EAAE,oBAAoB;QAC3B,OAAO,EAAE,cAAc;KACxB;IACD,aAAa,EAAE;QACb,MAAM,EAAE,eAAe;QACvB,aAAa,EAAE,UAAU;QACzB,SAAS,EAAE,UAAU;QACrB,YAAY,EAAE,+BAA+B;KAC9C;IACD,aAAa,EAAE;QACb,cAAc,EAAE,KAAK;QACrB,OAAO,EAAE,QAAQ;QACjB,UAAU,EAAE,UAAU;QACtB,SAAS,EAAE,cAAc;QACzB,OAAO,EAAE,MAAM;KAChB;IACD,eAAe,EAAE;QACf,QAAQ,EAAE,eAAe;QAEzB,MAAM,EAAE;YACN,OAAO,EAAE,6BAA6B;YACtC,QAAQ,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,UAAU,CAAC;YAErC,SAAS,EAAE;gBACT,GAAG,EAAE,oCAAoC;gBACzC,QAAQ,EAAE,gDAAgD;aAC3D;YAED,KAAK,EAAE,CAAC,UAAU,EAAE,iBAAiB,CAAC;YAEtC,aAAa,EAAE,iBAAiB;YAEhC,MAAM,EAAE;gBACN,IAAI,EAAE,4BAA4B;aACnC;SACF;QAED,QAAQ,EAAE;YACR,IAAI,EAAE,UAAU;YAEhB,QAAQ,EAAE;gBACR,2BAA2B;gBAC3B,yCAAyC;gBACzC,4CAA4C;aAC7C;YAED,KAAK,EAAE,IAAI;SACZ;QAED,QAAQ,EAAE;YACR,IAAI,EAAE,gBAAgB;YAEtB,SAAS,EAAE;gBACT,UAAU;gBACV,KAAK;gBACL,MAAM;gBACN,OAAO;gBACP,QAAQ;gBACR,OAAO;gBACP,UAAU;gBACV,MAAM;gBACN,MAAM;aACP;YAED,KAAK,EAAE;gBACL,6BAA6B;gBAC7B,qCAAqC;gBACrC,+BAA+B;aAChC;SACF;QAED,QAAQ,EAAE;YACR,EAAE,EAAE;gBACF,kBAAkB;gBAClB,0CAA0C;gBAC1C,oBAAoB;gBACpB,gDAAgD;gBAChD,+CAA+C;gBAC/C,2BAA2B;aAC5B;YAED,IAAI,EAAE;gBACJ,kCAAkC;gBAClC,uBAAuB;gBACvB,wCAAwC;aACzC;SACF;KACF;CACsC,CAAC;AAE1C,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,iBAAiB,EAAE;QACjB,KAAK;QACL,MAAM;QACN,KAAK;QACL,MAAM;QACN,OAAO;QACP,KAAK;QACL,MAAM;QACN,MAAM;QACN,OAAO;QACP,OAAO;KACR;IACD,kBAAkB,EAAE;QAClB,cAAc;QACd,OAAO;QACP,MAAM;QACN,OAAO;QACP,KAAK;QACL,UAAU;QACV,MAAM;KACP;IACD,YAAY,EAAE,MAAO;IACrB,SAAS,EAAE,GAAG;IACd,YAAY,EAAE,GAAG;CACgB,CAAC","sourcesContent":["import { ProjectConfigsConfig } from \"../../types/index/configs.types.js\";\nimport { IndexingConfig } from \"../../types/index/indexing.types.js\";\n\nexport const projectConfigs = {\n frameworkConfig: {\n name: \"Next.js\",\n router: \"App Router\",\n language: \"TypeScript\",\n icons: \"lucide-react icons\",\n styling: \"Tailwind CSS\",\n },\n runtimeConfig: {\n target: \"frontend-only\",\n serverActions: \"disabled\",\n apiRoutes: \"disabled\",\n dataFetching: \"client-side (fetch or mocked)\",\n },\n toolingConfig: {\n packageManager: \"npm\",\n linting: \"eslint\",\n formatting: \"prettier\",\n typecheck: \"tsc --noEmit\",\n testing: \"none\",\n },\n renderingConfig: {\n approach: \"config-driven\",\n\n routes: {\n meaning: \"URL pathname, not file path\",\n examples: [\"/\", \"/about\", \"/pricing\"],\n\n structure: {\n \"/\": \"app/page.tsx + app/pageConfig.json\",\n \"/about\": \"app/about/page.tsx + app/about/pageConfig.json\",\n },\n\n files: [\"page.tsx\", \"pageConfig.json\"],\n\n sourceOfTruth: \"pageConfig.json\",\n\n config: {\n root: \"elements: BuilderElement[]\",\n },\n },\n\n renderer: {\n file: \"page.tsx\",\n\n behavior: [\n \"imports ./pageConfig.json\",\n \"casts to { elements: BuilderElement[] }\",\n \"renders config.elements with RenderElement\",\n ],\n\n fixed: true,\n },\n\n elements: {\n type: \"BuilderElement\",\n\n supported: [\n \"fragment\",\n \"div\",\n \"text\",\n \"image\",\n \"button\",\n \"input\",\n \"textarea\",\n \"link\",\n \"icon\",\n ],\n\n rules: [\n \"Tailwind via className only\",\n \"supports children: BuilderElement[]\",\n \"unknown types render error UI\",\n ],\n },\n\n guidance: {\n do: [\n \"route = URL path\",\n \"edit only pageConfig.json for UI changes\",\n \"keep '/' populated\",\n \"RenderElement and BuilderElement already exist\",\n \"new routes require page.tsx + pageConfig.json\",\n \"prefer nested composition\",\n ],\n\n dont: [\n \"don't treat routes as file paths\",\n \"don't modify page.tsx\",\n \"don't generate React layout components\",\n ],\n },\n },\n} as const satisfies ProjectConfigsConfig;\n\nexport const indexing = {\n includeExtensions: [\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \".json\",\n \".md\",\n \".mdx\",\n \".css\",\n \".scss\",\n \".sass\",\n ],\n excludeDirectories: [\n \"node_modules\",\n \".next\",\n \"dist\",\n \"build\",\n \"out\",\n \"coverage\",\n \".git\",\n ],\n maxFileBytes: 200_000,\n chunkSize: 900,\n chunkOverlap: 150,\n} as const satisfies IndexingConfig;\n"]}
@@ -23,7 +23,15 @@ test("create_new_route: creates page.tsx and pageConfig.json under /app", async
23
23
  await fs.mkdir(path.join(workspaceRoot, "app", "dashboard"), { recursive: true });
24
24
  const impl = createCreateNewRouteImpl({ workspaceRoot, fs: makeRealFs() });
25
25
  const res = await impl("/dashboard", "settings");
26
- assert.equal(res, `created page.tsx successfully and content of pageConfig.json at route \"/dashboard/settings\":\n${DEFAULT_PAGE_CONFIG_JSON}`);
26
+ assert.deepEqual(res, {
27
+ success: true,
28
+ route: "/dashboard/settings",
29
+ created_files: [
30
+ "app/dashboard/settings/page.tsx",
31
+ "app/dashboard/settings/pageConfig.json",
32
+ ],
33
+ page_config_json: JSON.parse(DEFAULT_PAGE_CONFIG_JSON),
34
+ });
27
35
  const pageTsx = await fs.readFile(path.join(workspaceRoot, "app", "dashboard", "settings", "page.tsx"), "utf-8");
28
36
  const pageConfig = await fs.readFile(path.join(workspaceRoot, "app", "dashboard", "settings", "pageConfig.json"), "utf-8");
29
37
  assert.equal(pageTsx, PAGE_TSX_TEMPLATE_STRING);
@@ -48,7 +56,7 @@ test("create_new_route: atomic rollback on write failure", async () => {
48
56
  });
49
57
  const impl = createCreateNewRouteImpl({ workspaceRoot, fs: failingFs });
50
58
  const res = await impl("/dashboard", "settings");
51
- assert.equal(res, "failed to create");
59
+ assert.equal(res?.success, false);
52
60
  const entries = await fs.readdir(parentDir);
53
61
  assert.ok(!entries.includes("settings"), "final route dir should not exist");
54
62
  assert.ok(entries.every((e) => !e.startsWith(".qwintly_route_tmp_settings_")), "temp dir should be cleaned up");
@@ -63,7 +71,7 @@ test("create_new_route: fails when parent route folder does not exist", async ()
63
71
  await fs.mkdir(path.join(workspaceRoot, "app"), { recursive: true });
64
72
  const impl = createCreateNewRouteImpl({ workspaceRoot, fs: makeRealFs() });
65
73
  const res = await impl("/does-not-exist", "settings");
66
- assert.equal(res, "failed to create");
74
+ assert.deepEqual(res, { success: false, error: "Parent route missing" });
67
75
  }
68
76
  finally {
69
77
  await fs.rm(workspaceRoot, { recursive: true, force: true });
@@ -1 +1 @@
1
- {"version":3,"file":"createNewRoute.impl.test.js","sourceRoot":"","sources":["../../src/tests/createNewRoute.impl.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport test from \"node:test\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport fs from \"node:fs/promises\";\nimport {\n createCreateNewRouteImpl,\n DEFAULT_PAGE_CONFIG_JSON,\n PAGE_TSX_TEMPLATE_STRING,\n} from \"../ai/tools/implementations/createNewRoute.impl.js\";\n\ntype CoreFs = Parameters<typeof createCreateNewRouteImpl>[0][\"fs\"];\n\nconst makeRealFs = (overrides?: Partial<CoreFs>): CoreFs => {\n return {\n readFile: async (absolutePath) => fs.readFile(absolutePath, \"utf-8\"),\n writeFile: async (absolutePath, content) =>\n fs.writeFile(absolutePath, content ?? \"\", \"utf-8\"),\n mkdirp: async (absoluteDir) => {\n await fs.mkdir(absoluteDir, { recursive: true });\n },\n rmFile: async (absolutePath) => fs.rm(absolutePath, { force: true }),\n stat: async (absolutePath) => fs.stat(absolutePath),\n safeReadDir: async (absoluteDir) =>\n fs.readdir(absoluteDir, { withFileTypes: true }),\n ...(overrides ?? {}),\n };\n};\n\ntest(\"create_new_route: creates page.tsx and pageConfig.json under /app\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-\"));\n try {\n await fs.mkdir(path.join(workspaceRoot, \"app\", \"dashboard\"), { recursive: true });\n\n const impl = createCreateNewRouteImpl({ workspaceRoot, fs: makeRealFs() } as any);\n const res = await impl(\"/dashboard\", \"settings\");\n assert.equal(\n res,\n `created page.tsx successfully and content of pageConfig.json at route \\\"/dashboard/settings\\\":\\n${DEFAULT_PAGE_CONFIG_JSON}`,\n );\n\n const pageTsx = await fs.readFile(\n path.join(workspaceRoot, \"app\", \"dashboard\", \"settings\", \"page.tsx\"),\n \"utf-8\",\n );\n const pageConfig = await fs.readFile(\n path.join(workspaceRoot, \"app\", \"dashboard\", \"settings\", \"pageConfig.json\"),\n \"utf-8\",\n );\n\n assert.equal(pageTsx, PAGE_TSX_TEMPLATE_STRING);\n assert.equal(pageConfig, DEFAULT_PAGE_CONFIG_JSON);\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n\ntest(\"create_new_route: atomic rollback on write failure\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-\"));\n try {\n const parentDir = path.join(workspaceRoot, \"app\", \"dashboard\");\n await fs.mkdir(parentDir, { recursive: true });\n\n const failingFs = makeRealFs({\n writeFile: async (absolutePath, content) => {\n if (absolutePath.replace(/\\\\/g, \"/\").endsWith(\"/pageConfig.json\")) {\n throw new Error(\"simulated write failure\");\n }\n await fs.writeFile(absolutePath, content ?? \"\", \"utf-8\");\n },\n });\n\n const impl = createCreateNewRouteImpl({ workspaceRoot, fs: failingFs } as any);\n const res = await impl(\"/dashboard\", \"settings\");\n assert.equal(res, \"failed to create\");\n\n const entries = await fs.readdir(parentDir);\n assert.ok(!entries.includes(\"settings\"), \"final route dir should not exist\");\n assert.ok(\n entries.every((e) => !e.startsWith(\".qwintly_route_tmp_settings_\")),\n \"temp dir should be cleaned up\",\n );\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n\ntest(\"create_new_route: fails when parent route folder does not exist\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-\"));\n try {\n await fs.mkdir(path.join(workspaceRoot, \"app\"), { recursive: true });\n const impl = createCreateNewRouteImpl({ workspaceRoot, fs: makeRealFs() } as any);\n const res = await impl(\"/does-not-exist\", \"settings\");\n assert.equal(res, \"failed to create\");\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n"]}
1
+ {"version":3,"file":"createNewRoute.impl.test.js","sourceRoot":"","sources":["../../src/tests/createNewRoute.impl.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport test from \"node:test\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport fs from \"node:fs/promises\";\nimport {\n createCreateNewRouteImpl,\n DEFAULT_PAGE_CONFIG_JSON,\n PAGE_TSX_TEMPLATE_STRING,\n} from \"../ai/tools/implementations/createNewRoute.impl.js\";\n\ntype CoreFs = Parameters<typeof createCreateNewRouteImpl>[0][\"fs\"];\n\nconst makeRealFs = (overrides?: Partial<CoreFs>): CoreFs => {\n return {\n readFile: async (absolutePath) => fs.readFile(absolutePath, \"utf-8\"),\n writeFile: async (absolutePath, content) =>\n fs.writeFile(absolutePath, content ?? \"\", \"utf-8\"),\n mkdirp: async (absoluteDir) => {\n await fs.mkdir(absoluteDir, { recursive: true });\n },\n rmFile: async (absolutePath) => fs.rm(absolutePath, { force: true }),\n stat: async (absolutePath) => fs.stat(absolutePath),\n safeReadDir: async (absoluteDir) =>\n fs.readdir(absoluteDir, { withFileTypes: true }),\n ...(overrides ?? {}),\n };\n};\n\ntest(\"create_new_route: creates page.tsx and pageConfig.json under /app\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-\"));\n try {\n await fs.mkdir(path.join(workspaceRoot, \"app\", \"dashboard\"), { recursive: true });\n\n const impl = createCreateNewRouteImpl({ workspaceRoot, fs: makeRealFs() } as any);\n const res = await impl(\"/dashboard\", \"settings\");\n assert.deepEqual(res, {\n success: true,\n route: \"/dashboard/settings\",\n created_files: [\n \"app/dashboard/settings/page.tsx\",\n \"app/dashboard/settings/pageConfig.json\",\n ],\n page_config_json: JSON.parse(DEFAULT_PAGE_CONFIG_JSON),\n });\n\n const pageTsx = await fs.readFile(\n path.join(workspaceRoot, \"app\", \"dashboard\", \"settings\", \"page.tsx\"),\n \"utf-8\",\n );\n const pageConfig = await fs.readFile(\n path.join(workspaceRoot, \"app\", \"dashboard\", \"settings\", \"pageConfig.json\"),\n \"utf-8\",\n );\n\n assert.equal(pageTsx, PAGE_TSX_TEMPLATE_STRING);\n assert.equal(pageConfig, DEFAULT_PAGE_CONFIG_JSON);\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n\ntest(\"create_new_route: atomic rollback on write failure\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-\"));\n try {\n const parentDir = path.join(workspaceRoot, \"app\", \"dashboard\");\n await fs.mkdir(parentDir, { recursive: true });\n\n const failingFs = makeRealFs({\n writeFile: async (absolutePath, content) => {\n if (absolutePath.replace(/\\\\/g, \"/\").endsWith(\"/pageConfig.json\")) {\n throw new Error(\"simulated write failure\");\n }\n await fs.writeFile(absolutePath, content ?? \"\", \"utf-8\");\n },\n });\n\n const impl = createCreateNewRouteImpl({ workspaceRoot, fs: failingFs } as any);\n const res = await impl(\"/dashboard\", \"settings\");\n assert.equal((res as any)?.success, false);\n\n const entries = await fs.readdir(parentDir);\n assert.ok(!entries.includes(\"settings\"), \"final route dir should not exist\");\n assert.ok(\n entries.every((e) => !e.startsWith(\".qwintly_route_tmp_settings_\")),\n \"temp dir should be cleaned up\",\n );\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n\ntest(\"create_new_route: fails when parent route folder does not exist\", async () => {\n const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), \"qwintly-core-\"));\n try {\n await fs.mkdir(path.join(workspaceRoot, \"app\"), { recursive: true });\n const impl = createCreateNewRouteImpl({ workspaceRoot, fs: makeRealFs() } as any);\n const res = await impl(\"/does-not-exist\", \"settings\");\n assert.deepEqual(res, { success: false, error: \"Parent route missing\" });\n } finally {\n await fs.rm(workspaceRoot, { recursive: true, force: true });\n }\n});\n"]}
@@ -1,5 +1,4 @@
1
1
  export type * from "./index/configs.types.js";
2
- export type * from "./index/conventions.types.js";
3
2
  export type * from "./index/index.types.js";
4
3
  export type * from "./index/indexing.types.js";
5
4
  export type * from "./projectInfo.types.js";
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/types/public.ts"],"names":[],"mappings":"AACA,mBAAmB,0BAA0B,CAAC;AAC9C,mBAAmB,8BAA8B,CAAC;AAClD,mBAAmB,wBAAwB,CAAC;AAC5C,mBAAmB,2BAA2B,CAAC;AAC/C,mBAAmB,wBAAwB,CAAC;AAC5C,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/types/public.ts"],"names":[],"mappings":"AACA,mBAAmB,0BAA0B,CAAC;AAC9C,mBAAmB,wBAAwB,CAAC;AAC5C,mBAAmB,2BAA2B,CAAC;AAC/C,mBAAmB,wBAAwB,CAAC;AAC5C,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"public.js","sourceRoot":"","sources":["../../src/types/public.ts"],"names":[],"mappings":"AAMA,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC","sourcesContent":["// types/public.ts\r\nexport type * from \"./index/configs.types.js\";\r\nexport type * from \"./index/conventions.types.js\";\r\nexport type * from \"./index/index.types.js\";\r\nexport type * from \"./index/indexing.types.js\";\r\nexport type * from \"./projectInfo.types.js\";\r\nexport * from \"./context.types.js\";\r\nexport * from \"./events.js\";\r\nexport * from \"./updatePlan.types.js\";\r\n"]}
1
+ {"version":3,"file":"public.js","sourceRoot":"","sources":["../../src/types/public.ts"],"names":[],"mappings":"AAKA,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC","sourcesContent":["// types/public.ts\r\nexport type * from \"./index/configs.types.js\";\r\nexport type * from \"./index/index.types.js\";\r\nexport type * from \"./index/indexing.types.js\";\r\nexport type * from \"./projectInfo.types.js\";\r\nexport * from \"./context.types.js\";\r\nexport * from \"./events.js\";\r\nexport * from \"./updatePlan.types.js\";\r\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vedangiitb/qwintly-core",
3
- "version": "1.3.7",
3
+ "version": "1.3.9",
4
4
  "description": "Qwintly Core",
5
5
  "homepage": "https://github.com/vedangiitb/qwintly-core#readme",
6
6
  "bugs": {