@nextsparkjs/core 0.1.0-beta.192 → 0.1.0-beta.194

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 (161) hide show
  1. package/README.md +2 -2
  2. package/dist/components/app/layouts/PublicFooter.js +1 -1
  3. package/dist/components/app/layouts/PublicNavbar.js +1 -1
  4. package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
  5. package/dist/components/app/misc/ThemeToggle.js +4 -3
  6. package/dist/lib/api/auth/permissions.d.ts +10 -0
  7. package/dist/lib/api/auth/permissions.d.ts.map +1 -1
  8. package/dist/lib/api/auth/permissions.js +7 -0
  9. package/dist/lib/api/rate-limit.d.ts.map +1 -1
  10. package/dist/lib/api/rate-limit.js +3 -0
  11. package/dist/lib/api/request-origin.d.ts +26 -0
  12. package/dist/lib/api/request-origin.d.ts.map +1 -0
  13. package/dist/lib/api/request-origin.js +46 -0
  14. package/dist/lib/auth.d.ts.map +1 -1
  15. package/dist/lib/auth.js +8 -12
  16. package/dist/lib/config/features-types.d.ts +5 -4
  17. package/dist/lib/config/features-types.d.ts.map +1 -1
  18. package/dist/lib/entities/external-api-generator.d.ts.map +1 -1
  19. package/dist/lib/entities/external-api-generator.js +3 -1
  20. package/dist/lib/selectors/auth-sel.d.ts +2 -7
  21. package/dist/lib/selectors/auth-sel.d.ts.map +1 -1
  22. package/dist/lib/selectors/auth-sel.js +1 -3
  23. package/dist/lib/selectors/by-domain/auth.d.ts +8 -0
  24. package/dist/lib/selectors/by-domain/auth.d.ts.map +1 -0
  25. package/dist/lib/selectors/by-domain/auth.js +10 -0
  26. package/dist/lib/selectors/by-domain/block-editor.d.ts +8 -0
  27. package/dist/lib/selectors/by-domain/block-editor.d.ts.map +1 -0
  28. package/dist/lib/selectors/by-domain/block-editor.js +10 -0
  29. package/dist/lib/selectors/by-domain/common.d.ts +8 -0
  30. package/dist/lib/selectors/by-domain/common.d.ts.map +1 -0
  31. package/dist/lib/selectors/by-domain/common.js +10 -0
  32. package/dist/lib/selectors/by-domain/dashboard.d.ts +8 -0
  33. package/dist/lib/selectors/by-domain/dashboard.d.ts.map +1 -0
  34. package/dist/lib/selectors/by-domain/dashboard.js +10 -0
  35. package/dist/lib/selectors/by-domain/devtools.d.ts +8 -0
  36. package/dist/lib/selectors/by-domain/devtools.d.ts.map +1 -0
  37. package/dist/lib/selectors/by-domain/devtools.js +10 -0
  38. package/dist/lib/selectors/by-domain/entities.d.ts +8 -0
  39. package/dist/lib/selectors/by-domain/entities.d.ts.map +1 -0
  40. package/dist/lib/selectors/by-domain/entities.js +10 -0
  41. package/dist/lib/selectors/by-domain/global-search.d.ts +8 -0
  42. package/dist/lib/selectors/by-domain/global-search.d.ts.map +1 -0
  43. package/dist/lib/selectors/by-domain/global-search.js +10 -0
  44. package/dist/lib/selectors/by-domain/media.d.ts +8 -0
  45. package/dist/lib/selectors/by-domain/media.d.ts.map +1 -0
  46. package/dist/lib/selectors/by-domain/media.js +10 -0
  47. package/dist/lib/selectors/by-domain/patterns.d.ts +8 -0
  48. package/dist/lib/selectors/by-domain/patterns.d.ts.map +1 -0
  49. package/dist/lib/selectors/by-domain/patterns.js +10 -0
  50. package/dist/lib/selectors/by-domain/public.d.ts +8 -0
  51. package/dist/lib/selectors/by-domain/public.d.ts.map +1 -0
  52. package/dist/lib/selectors/by-domain/public.js +10 -0
  53. package/dist/lib/selectors/by-domain/settings.d.ts +8 -0
  54. package/dist/lib/selectors/by-domain/settings.d.ts.map +1 -0
  55. package/dist/lib/selectors/by-domain/settings.js +10 -0
  56. package/dist/lib/selectors/by-domain/superadmin.d.ts +8 -0
  57. package/dist/lib/selectors/by-domain/superadmin.d.ts.map +1 -0
  58. package/dist/lib/selectors/by-domain/superadmin.js +10 -0
  59. package/dist/lib/selectors/by-domain/taxonomies.d.ts +8 -0
  60. package/dist/lib/selectors/by-domain/taxonomies.d.ts.map +1 -0
  61. package/dist/lib/selectors/by-domain/taxonomies.js +10 -0
  62. package/dist/lib/selectors/by-domain/teams.d.ts +8 -0
  63. package/dist/lib/selectors/by-domain/teams.d.ts.map +1 -0
  64. package/dist/lib/selectors/by-domain/teams.js +10 -0
  65. package/dist/lib/selectors/core-selectors.d.ts +1 -0
  66. package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
  67. package/dist/lib/selectors/domains/public.selectors.d.ts +1 -0
  68. package/dist/lib/selectors/domains/public.selectors.d.ts.map +1 -1
  69. package/dist/lib/selectors/domains/public.selectors.js +3 -1
  70. package/dist/lib/selectors/selectors.d.ts +2 -0
  71. package/dist/lib/selectors/selectors.d.ts.map +1 -1
  72. package/dist/lib/services/subscription.service.d.ts.map +1 -1
  73. package/dist/lib/services/subscription.service.js +3 -0
  74. package/dist/lib/theme/ThemeProvider.d.ts.map +1 -1
  75. package/dist/lib/theme/ThemeProvider.js +7 -5
  76. package/dist/lib/utils/cors.d.ts +7 -0
  77. package/dist/lib/utils/cors.d.ts.map +1 -1
  78. package/dist/lib/utils/cors.js +8 -0
  79. package/dist/routes/(auth)/accept-invite/[token]/page.js +1 -1
  80. package/dist/routes/(auth)/forgot-password/page.js +1 -1
  81. package/dist/routes/(auth)/reset-password/page.js +1 -1
  82. package/dist/routes/(auth)/signup/page.cc.d.ts +2 -2
  83. package/dist/routes/(auth)/signup/page.d.ts +1 -1
  84. package/dist/routes/(auth)/signup/page.d.ts.map +1 -1
  85. package/dist/routes/(auth)/signup/page.js +1 -5
  86. package/dist/routes/(auth)/verify-email/page.js +1 -1
  87. package/dist/routes/(public)/docs/[section]/[page]/page.cc.js +1 -1
  88. package/dist/routes/(public)/docs/[section]/[page]/page.js +1 -1
  89. package/dist/routes/(public)/docs/layout.js +1 -1
  90. package/dist/routes/(public)/docs/page.js +1 -1
  91. package/dist/routes/_internal/default-public-layout.d.ts +5 -0
  92. package/dist/routes/_internal/default-public-layout.d.ts.map +1 -1
  93. package/dist/routes/_internal/default-public-layout.js +1 -2
  94. package/dist/routes/api/v1/users/[id]/meta/[key]/route.d.ts.map +1 -1
  95. package/dist/routes/api/v1/users/[id]/meta/[key]/route.js +13 -0
  96. package/dist/routes/api/v1/users/[id]/route.d.ts.map +1 -1
  97. package/dist/routes/api/v1/users/[id]/route.js +17 -0
  98. package/dist/routes/devtools/blocks/[slug]/page.d.ts +4 -1
  99. package/dist/routes/devtools/blocks/[slug]/page.d.ts.map +1 -1
  100. package/dist/routes/devtools/blocks/[slug]/page.js +2 -1
  101. package/dist/templates/features/blog/blocks/post-content/component.tsx +1 -1
  102. package/dist/templates/features/blog/entities/posts/posts.config.ts +0 -11
  103. package/dist/templates/features/pages/blocks/hero/component.tsx +1 -1
  104. package/dist/templates/features/pages/entities/pages/pages.config.ts +0 -11
  105. package/dist/templates/projects/blog/lib/block-selectors.ts +29 -0
  106. package/dist/templates/projects/blog/lib/selectors.ts +2 -9
  107. package/dist/templates/projects/blog/package.json +4 -7
  108. package/dist/templates/projects/crm/lib/block-selectors.ts +29 -0
  109. package/dist/templates/projects/crm/lib/selectors.ts +2 -9
  110. package/dist/templates/projects/crm/package.json +4 -4
  111. package/dist/templates/projects/crm/templates/dashboard/(main)/layout.tsx +2 -17
  112. package/dist/templates/projects/crm/templates/shared/CRMSidebarContext.tsx +23 -0
  113. package/dist/templates/projects/productivity/entities/boards/boards.service.ts +18 -18
  114. package/dist/templates/projects/productivity/entities/cards/cards.service.ts +18 -18
  115. package/dist/templates/projects/productivity/entities/lists/lists.service.ts +18 -18
  116. package/dist/templates/projects/productivity/lib/block-selectors.ts +29 -0
  117. package/dist/templates/projects/productivity/lib/selectors.ts +2 -9
  118. package/dist/templates/projects/productivity/package.json +4 -4
  119. package/dist/templates/projects/starter/lib/block-selectors.ts +28 -0
  120. package/dist/templates/projects/starter/lib/selectors.ts +2 -11
  121. package/package.json +12 -5
  122. package/scripts/build/auth-readiness.mjs +4 -0
  123. package/scripts/build/registry/__tests__/build-output.test.mjs +1 -1
  124. package/scripts/build/registry/__tests__/project-mode-root-first.test.mjs +6 -6
  125. package/scripts/build/registry/__tests__/write-places.test.mjs +1 -1
  126. package/scripts/build/registry/contracts/__tests__/contracts-wire.test.mjs +1 -1
  127. package/scripts/build/registry/contracts/__tests__/contracts.test.mjs +1 -1
  128. package/scripts/build/registry/contracts/render.mjs +3 -3
  129. package/scripts/build/registry/host/README.md +2 -2
  130. package/scripts/build/registry/host/__tests__/facade-emitter.test.mjs +9 -9
  131. package/scripts/build/registry/host/__tests__/host-mode.test.mjs +1 -1
  132. package/scripts/build/registry/host/__tests__/host-project-composition.test.mjs +1 -1
  133. package/scripts/build/registry/host/__tests__/host-project.test.mjs +2 -2
  134. package/scripts/build/registry/host/__tests__/host-robustness.test.mjs +65 -2
  135. package/scripts/build/registry/host/facade-emitter.mjs +2 -2
  136. package/scripts/build/registry/host/generation.mjs +54 -0
  137. package/scripts/build/registry/host/next-route-exports.json +2 -2
  138. package/scripts/build/registry/host/prepare.mjs +3 -0
  139. package/scripts/db/migration-time-limit.mjs +216 -34
  140. package/scripts/db/run-migrations.mjs +11 -179
  141. package/scripts/db/verify-theme-migrations.mjs +1 -1
  142. package/templates/features/blog/blocks/post-content/component.tsx +1 -1
  143. package/templates/features/blog/entities/posts/posts.config.ts +0 -11
  144. package/templates/features/pages/blocks/hero/component.tsx +1 -1
  145. package/templates/features/pages/entities/pages/pages.config.ts +0 -11
  146. package/templates/projects/blog/lib/block-selectors.ts +29 -0
  147. package/templates/projects/blog/lib/selectors.ts +2 -9
  148. package/templates/projects/blog/package.json +4 -7
  149. package/templates/projects/crm/lib/block-selectors.ts +29 -0
  150. package/templates/projects/crm/lib/selectors.ts +2 -9
  151. package/templates/projects/crm/package.json +4 -4
  152. package/templates/projects/crm/templates/dashboard/(main)/layout.tsx +2 -17
  153. package/templates/projects/crm/templates/shared/CRMSidebarContext.tsx +23 -0
  154. package/templates/projects/productivity/entities/boards/boards.service.ts +18 -18
  155. package/templates/projects/productivity/entities/cards/cards.service.ts +18 -18
  156. package/templates/projects/productivity/entities/lists/lists.service.ts +18 -18
  157. package/templates/projects/productivity/lib/block-selectors.ts +29 -0
  158. package/templates/projects/productivity/lib/selectors.ts +2 -9
  159. package/templates/projects/productivity/package.json +4 -4
  160. package/templates/projects/starter/lib/block-selectors.ts +28 -0
  161. package/templates/projects/starter/lib/selectors.ts +2 -11
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Block selectors, and a `sel` bound to them alone.
3
+ *
4
+ * Block components import `sel` from here, not from ./selectors: that file merges CORE_SELECTORS (every domain's
5
+ * map, ~10 kB gzip), and the loader that renders a page imports every block, so a block importing it puts the
6
+ * whole map on each public page. ./selectors re-exports BLOCK_SELECTORS, so Cypress still reaches the same paths.
7
+ */
8
+
9
+ import { createSelectorHelpers } from '@nextsparkjs/core/lib/selectors/selector-factory'
10
+
11
+ /**
12
+ * Block-specific selectors for the blog theme.
13
+ * Each block has at minimum a 'container' selector.
14
+ * Dynamic selectors use {index} placeholder.
15
+ */
16
+ export const BLOCK_SELECTORS = {
17
+ // Blocks the pages and blog content features copy into the project (blocks/hero, blocks/post-content)
18
+ hero: {
19
+ container: 'block-hero',
20
+ cta: 'hero-cta',
21
+ },
22
+ postContent: {
23
+ container: 'block-post-content',
24
+ divider: 'post-content-divider',
25
+ cta: 'post-content-cta',
26
+ },
27
+ } as const
28
+
29
+ export const { sel } = createSelectorHelpers({ blocks: BLOCK_SELECTORS })
@@ -10,20 +10,13 @@
10
10
  */
11
11
 
12
12
  import { createSelectorHelpers, CORE_SELECTORS } from '@nextsparkjs/core/selectors'
13
+ import { BLOCK_SELECTORS } from './block-selectors'
13
14
 
14
15
  // =============================================================================
15
16
  // BLOCK SELECTORS
16
17
  // =============================================================================
17
18
 
18
- /**
19
- * Block-specific selectors for the blog theme.
20
- * Each block has at minimum a 'container' selector.
21
- * Dynamic selectors use {index} placeholder.
22
- */
23
- export const BLOCK_SELECTORS = {
24
- // Blog theme currently has no custom blocks
25
- // Add block selectors here when blocks are created
26
- } as const
19
+ export { BLOCK_SELECTORS }
27
20
 
28
21
  // =============================================================================
29
22
  // ENTITY SELECTORS
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nextsparkjs/theme-blog",
3
3
  "version": "0.1.0-beta.192",
4
- "private": false,
4
+ "private": true,
5
5
  "type": "module",
6
6
  "main": "./config/theme.config.ts",
7
7
  "requiredPlugins": [],
@@ -14,17 +14,14 @@
14
14
  "@nextsparkjs/core": ">=0.1.0-0",
15
15
  "@nextsparkjs/testing": ">=0.1.0-0",
16
16
  "lucide-react": "^0.539.0",
17
- "next": "^15.0.0 || ^16.0.0",
17
+ "next": "~16.3.6",
18
18
  "next-intl": "^4.0.0",
19
- "react": "^19.0.0",
20
- "react-dom": "^19.0.0",
19
+ "react": "^19.2.0",
20
+ "react-dom": "^19.2.0",
21
21
  "zod": "^4.0.0"
22
22
  },
23
23
  "nextspark": {
24
24
  "type": "theme",
25
25
  "name": "blog"
26
- },
27
- "devDependencies": {
28
- "@types/dompurify": "^3.2.0"
29
26
  }
30
27
  }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Block selectors, and a `sel` bound to them alone.
3
+ *
4
+ * Block components import `sel` from here, not from ./selectors: that file merges CORE_SELECTORS (every domain's
5
+ * map, ~10 kB gzip), and the loader that renders a page imports every block, so a block importing it puts the
6
+ * whole map on each public page. ./selectors re-exports BLOCK_SELECTORS, so Cypress still reaches the same paths.
7
+ */
8
+
9
+ import { createSelectorHelpers } from '@nextsparkjs/core/lib/selectors/selector-factory'
10
+
11
+ /**
12
+ * Block-specific selectors for the CRM theme.
13
+ * Each block has at minimum a 'container' selector.
14
+ * Dynamic selectors use {index} placeholder.
15
+ */
16
+ export const BLOCK_SELECTORS = {
17
+ // Blocks the pages and blog content features copy into the project (blocks/hero, blocks/post-content)
18
+ hero: {
19
+ container: 'block-hero',
20
+ cta: 'hero-cta',
21
+ },
22
+ postContent: {
23
+ container: 'block-post-content',
24
+ divider: 'post-content-divider',
25
+ cta: 'post-content-cta',
26
+ },
27
+ } as const
28
+
29
+ export const { sel } = createSelectorHelpers({ blocks: BLOCK_SELECTORS })
@@ -10,20 +10,13 @@
10
10
  */
11
11
 
12
12
  import { createSelectorHelpers, CORE_SELECTORS } from '@nextsparkjs/core/selectors'
13
+ import { BLOCK_SELECTORS } from './block-selectors'
13
14
 
14
15
  // =============================================================================
15
16
  // BLOCK SELECTORS
16
17
  // =============================================================================
17
18
 
18
- /**
19
- * Block-specific selectors for the CRM theme.
20
- * Each block has at minimum a 'container' selector.
21
- * Dynamic selectors use {index} placeholder.
22
- */
23
- export const BLOCK_SELECTORS = {
24
- // CRM theme currently has no custom blocks
25
- // Add block selectors here when blocks are created
26
- } as const
19
+ export { BLOCK_SELECTORS }
27
20
 
28
21
  // =============================================================================
29
22
  // ENTITY SELECTORS
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nextsparkjs/theme-crm",
3
3
  "version": "0.1.0-beta.192",
4
- "private": false,
4
+ "private": true,
5
5
  "type": "module",
6
6
  "main": "./config/theme.config.ts",
7
7
  "requiredPlugins": [],
@@ -13,10 +13,10 @@
13
13
  "@nextsparkjs/core": ">=0.1.0-0",
14
14
  "@nextsparkjs/testing": ">=0.1.0-0",
15
15
  "lucide-react": "^0.539.0",
16
- "next": "^15.0.0 || ^16.0.0",
16
+ "next": "~16.3.6",
17
17
  "next-intl": "^4.0.0",
18
- "react": "^19.0.0",
19
- "react-dom": "^19.0.0",
18
+ "react": "^19.2.0",
19
+ "react-dom": "^19.2.0",
20
20
  "zod": "^4.0.0"
21
21
  },
22
22
  "nextspark": {
@@ -5,28 +5,13 @@
5
5
 
6
6
  'use client'
7
7
 
8
- import { useState, createContext, useContext, ReactNode, useEffect } from 'react'
8
+ import { useState, ReactNode, useEffect } from 'react'
9
9
  import { CRMSidebar } from '@/templates/shared/CRMSidebar'
10
10
  import { CRMTopBar } from '@/templates/shared/CRMTopBar'
11
11
  import { CRMMobileNav } from '@/templates/shared/CRMMobileNav'
12
+ import { SidebarContext } from '@/templates/shared/CRMSidebarContext'
12
13
  import { cn } from '@nextsparkjs/core/lib/utils'
13
14
 
14
- // Context for sidebar state
15
- interface SidebarContextValue {
16
- expanded: boolean
17
- setExpanded: (value: boolean) => void
18
- }
19
-
20
- const SidebarContext = createContext<SidebarContextValue | undefined>(undefined)
21
-
22
- export function useCRMSidebar() {
23
- const context = useContext(SidebarContext)
24
- if (!context) {
25
- return { expanded: false, setExpanded: () => {} }
26
- }
27
- return context
28
- }
29
-
30
15
  interface CRMDashboardLayoutProps {
31
16
  children: ReactNode
32
17
  }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * CRM sidebar state, shared by the dashboard layout (provider) and any component that reads it.
3
+ * Lives outside the layout: Next only allows a layout to export its default component.
4
+ */
5
+
6
+ 'use client'
7
+
8
+ import { createContext, useContext } from 'react'
9
+
10
+ export interface SidebarContextValue {
11
+ expanded: boolean
12
+ setExpanded: (value: boolean) => void
13
+ }
14
+
15
+ export const SidebarContext = createContext<SidebarContextValue | undefined>(undefined)
16
+
17
+ export function useCRMSidebar() {
18
+ const context = useContext(SidebarContext)
19
+ if (!context) {
20
+ return { expanded: false, setExpanded: () => {} }
21
+ }
22
+ return context
23
+ }
@@ -82,9 +82,9 @@ export class BoardsService {
82
82
  */
83
83
  static async getById(id: string, userId: string): Promise<Board | null> {
84
84
  const result = await queryOneWithRLS<DbBoard>(
85
- userId,
86
85
  `SELECT * FROM boards WHERE id = $1`,
87
- [id]
86
+ [id],
87
+ userId
88
88
  )
89
89
 
90
90
  return result ? mapDbBoard(result) : null
@@ -121,9 +121,9 @@ export class BoardsService {
121
121
 
122
122
  // Get total count
123
123
  const countResult = await queryOneWithRLS<{ count: string }>(
124
- userId,
125
124
  `SELECT COUNT(*) as count FROM boards ${whereClause}`,
126
- params
125
+ params,
126
+ userId
127
127
  )
128
128
  const total = parseInt(countResult?.count || '0', 10)
129
129
 
@@ -135,9 +135,9 @@ export class BoardsService {
135
135
 
136
136
  params.push(limit, offset)
137
137
  const boards = await queryWithRLS<DbBoard>(
138
- userId,
139
138
  `SELECT * FROM boards ${whereClause} ORDER BY ${validOrderBy} ${validOrderDir} LIMIT $${paramIndex++} OFFSET $${paramIndex++}`,
140
- params
139
+ params,
140
+ userId
141
141
  )
142
142
 
143
143
  return {
@@ -173,13 +173,13 @@ export class BoardsService {
173
173
  teamId,
174
174
  } = data
175
175
 
176
- const result = await mutateWithRLS<DbBoard>(
177
- userId,
176
+ const result = (await mutateWithRLS<DbBoard>(
178
177
  `INSERT INTO boards (name, description, color, is_archived, position, user_id, team_id)
179
178
  VALUES ($1, $2, $3, $4, $5, $6, $7)
180
179
  RETURNING *`,
181
- [name, description, color, isArchived, position, userId, teamId]
182
- )
180
+ [name, description, color, isArchived, position, userId, teamId],
181
+ userId
182
+ )).rows[0] ?? null
183
183
 
184
184
  if (!result) {
185
185
  throw new Error('Failed to create board')
@@ -228,11 +228,11 @@ export class BoardsService {
228
228
  updates.push(`updated_at = NOW()`)
229
229
  params.push(id)
230
230
 
231
- const result = await mutateWithRLS<DbBoard>(
232
- userId,
231
+ const result = (await mutateWithRLS<DbBoard>(
233
232
  `UPDATE boards SET ${updates.join(', ')} WHERE id = $${paramIndex} RETURNING *`,
234
- params
235
- )
233
+ params,
234
+ userId
235
+ )).rows[0] ?? null
236
236
 
237
237
  if (!result) {
238
238
  throw new Error('Board not found or access denied')
@@ -245,11 +245,11 @@ export class BoardsService {
245
245
  * Delete a board with RLS
246
246
  */
247
247
  static async delete(userId: string, id: string): Promise<boolean> {
248
- const result = await mutateWithRLS<DbBoard>(
249
- userId,
248
+ const result = (await mutateWithRLS<DbBoard>(
250
249
  `DELETE FROM boards WHERE id = $1 RETURNING *`,
251
- [id]
252
- )
250
+ [id],
251
+ userId
252
+ )).rows[0] ?? null
253
253
 
254
254
  return result !== null
255
255
  }
@@ -105,9 +105,9 @@ export class CardsService {
105
105
  */
106
106
  static async getById(id: string, userId: string): Promise<Card | null> {
107
107
  const result = await queryOneWithRLS<DbCard>(
108
- userId,
109
108
  `SELECT * FROM cards WHERE id = $1`,
110
- [id]
109
+ [id],
110
+ userId
111
111
  )
112
112
 
113
113
  return result ? mapDbCard(result) : null
@@ -162,9 +162,9 @@ export class CardsService {
162
162
 
163
163
  // Get total count
164
164
  const countResult = await queryOneWithRLS<{ count: string }>(
165
- userId,
166
165
  `SELECT COUNT(*) as count FROM cards ${whereClause}`,
167
- params
166
+ params,
167
+ userId
168
168
  )
169
169
  const total = parseInt(countResult?.count || '0', 10)
170
170
 
@@ -176,9 +176,9 @@ export class CardsService {
176
176
 
177
177
  params.push(limit, offset)
178
178
  const cards = await queryWithRLS<DbCard>(
179
- userId,
180
179
  `SELECT * FROM cards ${whereClause} ORDER BY ${validOrderBy} ${validOrderDir} LIMIT $${paramIndex++} OFFSET $${paramIndex++}`,
181
- params
180
+ params,
181
+ userId
182
182
  )
183
183
 
184
184
  return {
@@ -218,13 +218,13 @@ export class CardsService {
218
218
  teamId,
219
219
  } = data
220
220
 
221
- const result = await mutateWithRLS<DbCard>(
222
- userId,
221
+ const result = (await mutateWithRLS<DbCard>(
223
222
  `INSERT INTO cards (title, description, list_id, board_id, position, due_date, assignee_id, labels, is_archived, user_id, team_id)
224
223
  VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
225
224
  RETURNING *`,
226
- [title, description, listId, boardId, position, dueDate, assigneeId, JSON.stringify(labels), isArchived, userId, teamId]
227
- )
225
+ [title, description, listId, boardId, position, dueDate, assigneeId, JSON.stringify(labels), isArchived, userId, teamId],
226
+ userId
227
+ )).rows[0] ?? null
228
228
 
229
229
  if (!result) {
230
230
  throw new Error('Failed to create card')
@@ -293,11 +293,11 @@ export class CardsService {
293
293
  updates.push(`updated_at = NOW()`)
294
294
  params.push(id)
295
295
 
296
- const result = await mutateWithRLS<DbCard>(
297
- userId,
296
+ const result = (await mutateWithRLS<DbCard>(
298
297
  `UPDATE cards SET ${updates.join(', ')} WHERE id = $${paramIndex} RETURNING *`,
299
- params
300
- )
298
+ params,
299
+ userId
300
+ )).rows[0] ?? null
301
301
 
302
302
  if (!result) {
303
303
  throw new Error('Card not found or access denied')
@@ -325,11 +325,11 @@ export class CardsService {
325
325
  * Delete a card with RLS
326
326
  */
327
327
  static async delete(userId: string, id: string): Promise<boolean> {
328
- const result = await mutateWithRLS<DbCard>(
329
- userId,
328
+ const result = (await mutateWithRLS<DbCard>(
330
329
  `DELETE FROM cards WHERE id = $1 RETURNING *`,
331
- [id]
332
- )
330
+ [id],
331
+ userId
332
+ )).rows[0] ?? null
333
333
 
334
334
  return result !== null
335
335
  }
@@ -78,9 +78,9 @@ export class ListsService {
78
78
  */
79
79
  static async getById(id: string, userId: string): Promise<List | null> {
80
80
  const result = await queryOneWithRLS<DbList>(
81
- userId,
82
81
  `SELECT * FROM lists WHERE id = $1`,
83
- [id]
82
+ [id],
83
+ userId
84
84
  )
85
85
 
86
86
  return result ? mapDbList(result) : null
@@ -123,9 +123,9 @@ export class ListsService {
123
123
 
124
124
  // Get total count
125
125
  const countResult = await queryOneWithRLS<{ count: string }>(
126
- userId,
127
126
  `SELECT COUNT(*) as count FROM lists ${whereClause}`,
128
- params
127
+ params,
128
+ userId
129
129
  )
130
130
  const total = parseInt(countResult?.count || '0', 10)
131
131
 
@@ -137,9 +137,9 @@ export class ListsService {
137
137
 
138
138
  params.push(limit, offset)
139
139
  const lists = await queryWithRLS<DbList>(
140
- userId,
141
140
  `SELECT * FROM lists ${whereClause} ORDER BY ${validOrderBy} ${validOrderDir} LIMIT $${paramIndex++} OFFSET $${paramIndex++}`,
142
- params
141
+ params,
142
+ userId
143
143
  )
144
144
 
145
145
  return {
@@ -174,13 +174,13 @@ export class ListsService {
174
174
  teamId,
175
175
  } = data
176
176
 
177
- const result = await mutateWithRLS<DbList>(
178
- userId,
177
+ const result = (await mutateWithRLS<DbList>(
179
178
  `INSERT INTO lists (name, board_id, position, is_archived, user_id, team_id)
180
179
  VALUES ($1, $2, $3, $4, $5, $6)
181
180
  RETURNING *`,
182
- [name, boardId, position, isArchived, userId, teamId]
183
- )
181
+ [name, boardId, position, isArchived, userId, teamId],
182
+ userId
183
+ )).rows[0] ?? null
184
184
 
185
185
  if (!result) {
186
186
  throw new Error('Failed to create list')
@@ -224,11 +224,11 @@ export class ListsService {
224
224
  updates.push(`updated_at = NOW()`)
225
225
  params.push(id)
226
226
 
227
- const result = await mutateWithRLS<DbList>(
228
- userId,
227
+ const result = (await mutateWithRLS<DbList>(
229
228
  `UPDATE lists SET ${updates.join(', ')} WHERE id = $${paramIndex} RETURNING *`,
230
- params
231
- )
229
+ params,
230
+ userId
231
+ )).rows[0] ?? null
232
232
 
233
233
  if (!result) {
234
234
  throw new Error('List not found or access denied')
@@ -241,11 +241,11 @@ export class ListsService {
241
241
  * Delete a list with RLS
242
242
  */
243
243
  static async delete(userId: string, id: string): Promise<boolean> {
244
- const result = await mutateWithRLS<DbList>(
245
- userId,
244
+ const result = (await mutateWithRLS<DbList>(
246
245
  `DELETE FROM lists WHERE id = $1 RETURNING *`,
247
- [id]
248
- )
246
+ [id],
247
+ userId
248
+ )).rows[0] ?? null
249
249
 
250
250
  return result !== null
251
251
  }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Block selectors, and a `sel` bound to them alone.
3
+ *
4
+ * Block components import `sel` from here, not from ./selectors: that file merges CORE_SELECTORS (every domain's
5
+ * map, ~10 kB gzip), and the loader that renders a page imports every block, so a block importing it puts the
6
+ * whole map on each public page. ./selectors re-exports BLOCK_SELECTORS, so Cypress still reaches the same paths.
7
+ */
8
+
9
+ import { createSelectorHelpers } from '@nextsparkjs/core/lib/selectors/selector-factory'
10
+
11
+ /**
12
+ * Block-specific selectors for the productivity theme.
13
+ * Each block has at minimum a 'container' selector.
14
+ * Dynamic selectors use {index} placeholder.
15
+ */
16
+ export const BLOCK_SELECTORS = {
17
+ // Blocks the pages and blog content features copy into the project (blocks/hero, blocks/post-content)
18
+ hero: {
19
+ container: 'block-hero',
20
+ cta: 'hero-cta',
21
+ },
22
+ postContent: {
23
+ container: 'block-post-content',
24
+ divider: 'post-content-divider',
25
+ cta: 'post-content-cta',
26
+ },
27
+ } as const
28
+
29
+ export const { sel } = createSelectorHelpers({ blocks: BLOCK_SELECTORS })
@@ -10,20 +10,13 @@
10
10
  */
11
11
 
12
12
  import { createSelectorHelpers, CORE_SELECTORS } from '@nextsparkjs/core/selectors'
13
+ import { BLOCK_SELECTORS } from './block-selectors'
13
14
 
14
15
  // =============================================================================
15
16
  // BLOCK SELECTORS
16
17
  // =============================================================================
17
18
 
18
- /**
19
- * Block-specific selectors for the productivity theme.
20
- * Each block has at minimum a 'container' selector.
21
- * Dynamic selectors use {index} placeholder.
22
- */
23
- export const BLOCK_SELECTORS = {
24
- // Productivity theme currently has no custom blocks
25
- // Add block selectors here when blocks are created
26
- } as const
19
+ export { BLOCK_SELECTORS }
27
20
 
28
21
  // =============================================================================
29
22
  // ENTITY SELECTORS
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nextsparkjs/theme-productivity",
3
3
  "version": "0.1.0-beta.192",
4
- "private": false,
4
+ "private": true,
5
5
  "type": "module",
6
6
  "main": "./config/theme.config.ts",
7
7
  "requiredPlugins": [],
@@ -17,9 +17,9 @@
17
17
  "@nextsparkjs/core": ">=0.1.0-0",
18
18
  "@nextsparkjs/testing": ">=0.1.0-0",
19
19
  "lucide-react": "^0.539.0",
20
- "next": "^15.0.0 || ^16.0.0",
21
- "react": "^19.0.0",
22
- "react-dom": "^19.0.0",
20
+ "next": "~16.3.6",
21
+ "react": "^19.2.0",
22
+ "react-dom": "^19.2.0",
23
23
  "zod": "^4.0.0"
24
24
  },
25
25
  "nextspark": {
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Block selectors, and a `sel` bound to them alone.
3
+ *
4
+ * Block components import `sel` from here, not from ./selectors: that file merges CORE_SELECTORS (every domain's
5
+ * map, ~10 kB gzip), and the loader that renders a page imports every block, so a block importing it puts the
6
+ * whole map on each public page. ./selectors re-exports BLOCK_SELECTORS, so Cypress still reaches the same paths.
7
+ */
8
+
9
+ import { createSelectorHelpers } from '@nextsparkjs/core/lib/selectors/selector-factory'
10
+
11
+ /**
12
+ * Block-specific selectors for the starter theme.
13
+ * Each block has at minimum a 'container' selector.
14
+ * Dynamic selectors use {index} placeholder.
15
+ */
16
+ export const BLOCK_SELECTORS = {
17
+ hero: {
18
+ container: 'block-hero',
19
+ cta: 'hero-cta',
20
+ },
21
+ postContent: {
22
+ container: 'block-post-content',
23
+ divider: 'post-content-divider',
24
+ cta: 'post-content-cta',
25
+ },
26
+ } as const
27
+
28
+ export const { sel } = createSelectorHelpers({ blocks: BLOCK_SELECTORS })
@@ -10,22 +10,13 @@
10
10
  */
11
11
 
12
12
  import { createSelectorHelpers, CORE_SELECTORS } from '@nextsparkjs/core/selectors'
13
+ import { BLOCK_SELECTORS } from './block-selectors'
13
14
 
14
15
  // =============================================================================
15
16
  // BLOCK SELECTORS
16
17
  // =============================================================================
17
18
 
18
- /**
19
- * Block-specific selectors for the starter theme.
20
- * Each block has at minimum a 'container' selector.
21
- * Dynamic selectors use {index} placeholder.
22
- */
23
- export const BLOCK_SELECTORS = {
24
- hero: {
25
- container: 'block-hero',
26
- cta: 'hero-cta',
27
- },
28
- } as const
19
+ export { BLOCK_SELECTORS }
29
20
 
30
21
  // =============================================================================
31
22
  // THEME SELECTORS (CORE + BLOCKS)