@forwardimpact/pathway 0.21.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/README.md +3 -3
  2. package/bin/fit-pathway.js +22 -22
  3. package/package.json +4 -3
  4. package/src/commands/agent.js +14 -10
  5. package/src/commands/behaviour.js +11 -1
  6. package/src/commands/build.js +11 -2
  7. package/src/commands/command-factory.js +4 -2
  8. package/src/commands/dev.js +9 -2
  9. package/src/commands/discipline.js +19 -2
  10. package/src/commands/driver.js +11 -1
  11. package/src/commands/index.js +1 -1
  12. package/src/commands/init.js +1 -1
  13. package/src/commands/interview.js +8 -8
  14. package/src/commands/job.js +41 -28
  15. package/src/commands/level.js +76 -0
  16. package/src/commands/progress.js +20 -20
  17. package/src/commands/questions.js +3 -3
  18. package/src/commands/skill.js +11 -1
  19. package/src/commands/stage.js +11 -1
  20. package/src/commands/tool.js +4 -3
  21. package/src/commands/track.js +11 -1
  22. package/src/components/action-buttons.js +3 -3
  23. package/src/components/builder.js +25 -25
  24. package/src/components/card.js +8 -104
  25. package/src/components/comparison-radar.js +4 -4
  26. package/src/components/detail.js +18 -120
  27. package/src/components/error-page.js +8 -68
  28. package/src/components/grid.js +12 -106
  29. package/src/components/list.js +7 -116
  30. package/src/components/nav.js +7 -60
  31. package/src/components/radar-chart.js +3 -3
  32. package/src/components/skill-matrix.js +7 -7
  33. package/src/css/bundles/app.css +25 -21
  34. package/src/css/bundles/handout.css +33 -33
  35. package/src/css/bundles/slides.css +25 -25
  36. package/src/css/pages/landing.css +5 -5
  37. package/src/formatters/index.js +5 -5
  38. package/src/formatters/interview/shared.js +23 -23
  39. package/src/formatters/job/description.js +18 -18
  40. package/src/formatters/job/dom.js +12 -12
  41. package/src/formatters/job/markdown.js +7 -7
  42. package/src/formatters/json-ld.js +24 -24
  43. package/src/formatters/{grade → level}/dom.js +31 -27
  44. package/src/formatters/{grade → level}/markdown.js +19 -28
  45. package/src/formatters/{grade → level}/microdata.js +28 -38
  46. package/src/formatters/level/shared.js +86 -0
  47. package/src/formatters/progress/markdown.js +2 -2
  48. package/src/formatters/progress/shared.js +51 -51
  49. package/src/formatters/questions/markdown.js +8 -6
  50. package/src/formatters/questions/shared.js +7 -7
  51. package/src/formatters/skill/dom.js +4 -4
  52. package/src/formatters/skill/markdown.js +1 -1
  53. package/src/formatters/skill/microdata.js +3 -3
  54. package/src/formatters/skill/shared.js +3 -3
  55. package/src/formatters/track/shared.js +1 -1
  56. package/src/handout-main.js +12 -12
  57. package/src/handout.html +32 -13
  58. package/src/index.html +33 -14
  59. package/src/lib/card-mappers.js +16 -16
  60. package/src/lib/cli-command.js +3 -3
  61. package/src/lib/cli-output.js +2 -2
  62. package/src/lib/error-boundary.js +3 -66
  63. package/src/lib/errors.js +7 -45
  64. package/src/lib/job-cache.js +12 -12
  65. package/src/lib/markdown.js +2 -109
  66. package/src/lib/reactive.js +7 -73
  67. package/src/lib/render.js +53 -201
  68. package/src/lib/router-core.js +2 -156
  69. package/src/lib/router-pages.js +2 -11
  70. package/src/lib/router-slides.js +2 -197
  71. package/src/lib/state.js +16 -65
  72. package/src/lib/utils.js +3 -10
  73. package/src/lib/yaml-loader.js +22 -80
  74. package/src/main.js +10 -10
  75. package/src/pages/agent-builder.js +12 -12
  76. package/src/pages/assessment-results.js +28 -24
  77. package/src/pages/interview-builder.js +6 -6
  78. package/src/pages/interview.js +8 -8
  79. package/src/pages/job-builder.js +7 -7
  80. package/src/pages/job.js +8 -8
  81. package/src/pages/landing.js +8 -8
  82. package/src/pages/level.js +122 -0
  83. package/src/pages/progress-builder.js +8 -8
  84. package/src/pages/progress.js +74 -74
  85. package/src/pages/self-assessment.js +7 -7
  86. package/src/pages/skill.js +1 -1
  87. package/src/slide-main.js +23 -23
  88. package/src/slides/chapter.js +4 -4
  89. package/src/slides/index.js +11 -11
  90. package/src/slides/interview.js +2 -2
  91. package/src/slides/job.js +4 -4
  92. package/src/slides/level.js +32 -0
  93. package/src/slides/overview.js +10 -10
  94. package/src/slides/progress.js +13 -13
  95. package/src/slides.html +32 -13
  96. package/src/types.js +1 -1
  97. package/templates/job.template.md +2 -2
  98. package/src/commands/grade.js +0 -60
  99. package/src/css/base.css +0 -56
  100. package/src/css/components/badges.css +0 -232
  101. package/src/css/components/buttons.css +0 -101
  102. package/src/css/components/forms.css +0 -191
  103. package/src/css/components/layout.css +0 -218
  104. package/src/css/components/nav.css +0 -206
  105. package/src/css/components/progress.css +0 -166
  106. package/src/css/components/states.css +0 -82
  107. package/src/css/components/surfaces.css +0 -347
  108. package/src/css/components/tables.css +0 -362
  109. package/src/css/components/top-bar.css +0 -180
  110. package/src/css/components/typography.css +0 -121
  111. package/src/css/components/utilities.css +0 -41
  112. package/src/css/pages/detail.css +0 -119
  113. package/src/css/reset.css +0 -50
  114. package/src/css/tokens.css +0 -162
  115. package/src/css/views/handout.css +0 -30
  116. package/src/css/views/print.css +0 -634
  117. package/src/css/views/slide-animations.css +0 -113
  118. package/src/css/views/slide-base.css +0 -331
  119. package/src/css/views/slide-sections.css +0 -597
  120. package/src/css/views/slide-tables.css +0 -275
  121. package/src/formatters/grade/shared.js +0 -86
  122. package/src/pages/grade.js +0 -122
  123. package/src/slides/grade.js +0 -32
package/src/index.html CHANGED
@@ -25,19 +25,38 @@
25
25
  "@forwardimpact/map/levels": "/map/lib/levels.js",
26
26
  "@forwardimpact/map/loader": "/map/lib/loader.js",
27
27
  "@forwardimpact/map/validation": "/map/lib/validation.js",
28
- "@forwardimpact/libpathway": "/model/lib/index.js",
29
- "@forwardimpact/libpathway/derivation": "/model/lib/derivation.js",
30
- "@forwardimpact/libpathway/modifiers": "/model/lib/modifiers.js",
31
- "@forwardimpact/libpathway/agent": "/model/lib/agent.js",
32
- "@forwardimpact/libpathway/interview": "/model/lib/interview.js",
33
- "@forwardimpact/libpathway/job": "/model/lib/job.js",
34
- "@forwardimpact/libpathway/job-cache": "/model/lib/job-cache.js",
35
- "@forwardimpact/libpathway/checklist": "/model/lib/checklist.js",
36
- "@forwardimpact/libpathway/matching": "/model/lib/matching.js",
37
- "@forwardimpact/libpathway/profile": "/model/lib/profile.js",
38
- "@forwardimpact/libpathway/progression": "/model/lib/progression.js",
39
- "@forwardimpact/libpathway/policies": "/model/lib/policies/index.js",
40
- "@forwardimpact/libpathway/toolkit": "/model/lib/toolkit.js"
28
+ "@forwardimpact/libskill": "/model/lib/index.js",
29
+ "@forwardimpact/libskill/derivation": "/model/lib/derivation.js",
30
+ "@forwardimpact/libskill/modifiers": "/model/lib/modifiers.js",
31
+ "@forwardimpact/libskill/agent": "/model/lib/agent.js",
32
+ "@forwardimpact/libskill/interview": "/model/lib/interview.js",
33
+ "@forwardimpact/libskill/job": "/model/lib/job.js",
34
+ "@forwardimpact/libskill/job-cache": "/model/lib/job-cache.js",
35
+ "@forwardimpact/libskill/checklist": "/model/lib/checklist.js",
36
+ "@forwardimpact/libskill/matching": "/model/lib/matching.js",
37
+ "@forwardimpact/libskill/profile": "/model/lib/profile.js",
38
+ "@forwardimpact/libskill/progression": "/model/lib/progression.js",
39
+ "@forwardimpact/libskill/policies": "/model/lib/policies/index.js",
40
+ "@forwardimpact/libskill/toolkit": "/model/lib/toolkit.js",
41
+ "@forwardimpact/libui": "/ui/lib/index.js",
42
+ "@forwardimpact/libui/render": "/ui/lib/render.js",
43
+ "@forwardimpact/libui/reactive": "/ui/lib/reactive.js",
44
+ "@forwardimpact/libui/state": "/ui/lib/state.js",
45
+ "@forwardimpact/libui/errors": "/ui/lib/errors.js",
46
+ "@forwardimpact/libui/error-boundary": "/ui/lib/error-boundary.js",
47
+ "@forwardimpact/libui/router-core": "/ui/lib/router-core.js",
48
+ "@forwardimpact/libui/router-pages": "/ui/lib/router-pages.js",
49
+ "@forwardimpact/libui/router-slides": "/ui/lib/router-slides.js",
50
+ "@forwardimpact/libui/yaml-loader": "/ui/lib/yaml-loader.js",
51
+ "@forwardimpact/libui/markdown": "/ui/lib/markdown.js",
52
+ "@forwardimpact/libui/utils": "/ui/lib/utils.js",
53
+ "@forwardimpact/libui/components": "/ui/lib/components/index.js",
54
+ "@forwardimpact/libui/components/card": "/ui/lib/components/card.js",
55
+ "@forwardimpact/libui/components/grid": "/ui/lib/components/grid.js",
56
+ "@forwardimpact/libui/components/list": "/ui/lib/components/list.js",
57
+ "@forwardimpact/libui/components/detail": "/ui/lib/components/detail.js",
58
+ "@forwardimpact/libui/components/nav": "/ui/lib/components/nav.js",
59
+ "@forwardimpact/libui/components/error-page": "/ui/lib/components/error-page.js"
41
60
  }
42
61
  }
43
62
  </script>
@@ -83,7 +102,7 @@
83
102
  <div class="drawer-section">
84
103
  <span class="drawer-section-label">Browse</span>
85
104
  <a href="#/discipline">Disciplines</a>
86
- <a href="#/grade">Grades</a>
105
+ <a href="#/level">Levels</a>
87
106
  <a href="#/track">Tracks</a>
88
107
  <a href="#/behaviour">Behaviours</a>
89
108
  <a href="#/skill">Skills</a>
@@ -109,31 +109,31 @@ export function driverToCardConfig(driver) {
109
109
  }
110
110
 
111
111
  /**
112
- * Map grade to card config (for timeline)
113
- * @param {Object} grade
112
+ * Map level to card config (for timeline)
113
+ * @param {Object} level
114
114
  * @returns {Object}
115
115
  */
116
- export function gradeToCardConfig(grade) {
116
+ export function levelToCardConfig(level) {
117
117
  return {
118
- title: grade.displayName,
119
- description: grade.scope || grade.truncatedDescription,
120
- href: `/grade/${grade.id}`,
121
- badges: [createBadge(grade.id, "default")],
118
+ title: level.displayName,
119
+ description: level.scope || level.truncatedDescription,
120
+ href: `/level/${level.id}`,
121
+ badges: [createBadge(level.id, "default")],
122
122
  meta: [
123
123
  createBadge(
124
- `Primary: ${formatLevel(grade.baseSkillLevels?.primary)}`,
124
+ `Primary: ${formatLevel(level.baseSkillProficiencies?.primary)}`,
125
125
  "primary",
126
126
  ),
127
127
  createBadge(
128
- `Secondary: ${formatLevel(grade.baseSkillLevels?.secondary)}`,
128
+ `Secondary: ${formatLevel(level.baseSkillProficiencies?.secondary)}`,
129
129
  "secondary",
130
130
  ),
131
131
  createBadge(
132
- `Broad: ${formatLevel(grade.baseSkillLevels?.broad)}`,
132
+ `Broad: ${formatLevel(level.baseSkillProficiencies?.broad)}`,
133
133
  "broad",
134
134
  ),
135
135
  ],
136
- yearsExperience: grade.yearsExperience,
136
+ yearsExperience: level.yearsExperience,
137
137
  };
138
138
  }
139
139
 
@@ -158,15 +158,15 @@ export function trackToCardConfig(track) {
158
158
  */
159
159
  export function jobToCardConfig(job) {
160
160
  const href = job.track
161
- ? `/job/${job.discipline.id}/${job.grade.id}/${job.track.id}`
162
- : `/job/${job.discipline.id}/${job.grade.id}`;
161
+ ? `/job/${job.discipline.id}/${job.level.id}/${job.track.id}`
162
+ : `/job/${job.discipline.id}/${job.level.id}`;
163
163
  return {
164
164
  title: job.title,
165
165
  description: job.track
166
- ? `${job.discipline.specialization || job.discipline.name} at ${job.grade.professionalTitle} level on ${job.track.name} track`
167
- : `${job.discipline.specialization || job.discipline.name} at ${job.grade.professionalTitle} level`,
166
+ ? `${job.discipline.specialization || job.discipline.name} at ${job.level.professionalTitle} level on ${job.track.name} track`
167
+ : `${job.discipline.specialization || job.discipline.name} at ${job.level.professionalTitle} level`,
168
168
  href,
169
- badges: [createBadge(job.grade.id, "default")],
169
+ badges: [createBadge(job.level.id, "default")],
170
170
  meta: job.track ? [createBadge(job.track.name, "secondary")] : [],
171
171
  };
172
172
  }
@@ -21,7 +21,7 @@ const ROUTE_COMMANDS = [
21
21
  toCommand: () => "npx fit-pathway discipline",
22
22
  },
23
23
  { pattern: /^\/track$/, toCommand: () => "npx fit-pathway track" },
24
- { pattern: /^\/grade$/, toCommand: () => "npx fit-pathway grade" },
24
+ { pattern: /^\/level$/, toCommand: () => "npx fit-pathway level" },
25
25
  { pattern: /^\/driver$/, toCommand: () => "npx fit-pathway driver" },
26
26
  { pattern: /^\/stage$/, toCommand: () => "npx fit-pathway stage" },
27
27
  { pattern: /^\/tool$/, toCommand: () => "npx fit-pathway tool" },
@@ -44,8 +44,8 @@ const ROUTE_COMMANDS = [
44
44
  toCommand: (m) => `npx fit-pathway track ${m[1]}`,
45
45
  },
46
46
  {
47
- pattern: /^\/grade\/(.+)$/,
48
- toCommand: (m) => `npx fit-pathway grade ${m[1]}`,
47
+ pattern: /^\/level\/(.+)$/,
48
+ toCommand: (m) => `npx fit-pathway level ${m[1]}`,
49
49
  },
50
50
  {
51
51
  pattern: /^\/driver\/(.+)$/,
@@ -126,11 +126,11 @@ export function formatTable(headers, rows, options = {}) {
126
126
  }
127
127
 
128
128
  /**
129
- * Format skill level with color
129
+ * Format skill proficiency with color
130
130
  * @param {string} level
131
131
  * @returns {string}
132
132
  */
133
- export function formatSkillLevel(level) {
133
+ export function formatSkillProficiency(level) {
134
134
  const levelColors = {
135
135
  awareness: colors.gray,
136
136
  foundational: colors.blue,
@@ -1,70 +1,7 @@
1
1
  /**
2
2
  * Error boundary wrapper for page rendering
3
+ *
4
+ * Re-exports from @forwardimpact/libui/error-boundary.
3
5
  */
4
6
 
5
- import { renderNotFound, renderError } from "../components/error-page.js";
6
- import { NotFoundError, InvalidCombinationError } from "./errors.js";
7
-
8
- /**
9
- * @typedef {Object} ErrorBoundaryOptions
10
- * @property {(error: Error) => void} [onError] - Error callback for logging
11
- * @property {string} [backPath] - Default back path
12
- * @property {string} [backText] - Default back text
13
- * @property {(title: string, message: string) => void} [renderErrorFn] - Custom error renderer
14
- */
15
-
16
- /**
17
- * Wrap a render function with error handling
18
- * @param {Function} renderFn - Page render function
19
- * @param {ErrorBoundaryOptions} [options]
20
- * @returns {Function}
21
- */
22
- export function withErrorBoundary(renderFn, options = {}) {
23
- const errorRenderer =
24
- options.renderErrorFn ||
25
- ((title, message) => {
26
- renderError({
27
- title,
28
- message,
29
- backPath: options.backPath || "/",
30
- backText: options.backText || "← Back to Home",
31
- });
32
- });
33
-
34
- return (...args) => {
35
- try {
36
- return renderFn(...args);
37
- } catch (error) {
38
- console.error("Page render error:", error);
39
-
40
- options.onError?.(error);
41
-
42
- if (error instanceof NotFoundError) {
43
- if (options.renderErrorFn) {
44
- errorRenderer(
45
- `${error.entityType} Not Found`,
46
- `No ${error.entityType.toLowerCase()} found with ID: ${error.entityId}`,
47
- );
48
- } else {
49
- renderNotFound({
50
- entityType: error.entityType,
51
- entityId: error.entityId,
52
- backPath: error.backPath,
53
- backText: `← Back to ${error.entityType}s`,
54
- });
55
- }
56
- return;
57
- }
58
-
59
- if (error instanceof InvalidCombinationError) {
60
- errorRenderer("Invalid Combination", error.message);
61
- return;
62
- }
63
-
64
- errorRenderer(
65
- "Something Went Wrong",
66
- error.message || "An unexpected error occurred.",
67
- );
68
- }
69
- };
70
- }
7
+ export { withErrorBoundary } from "@forwardimpact/libui/error-boundary";
package/src/lib/errors.js CHANGED
@@ -1,49 +1,11 @@
1
1
  /**
2
2
  * Custom error types for the application
3
+ *
4
+ * Re-exports from @forwardimpact/libui/errors.
3
5
  */
4
6
 
5
- /**
6
- * Entity not found error
7
- */
8
- export class NotFoundError extends Error {
9
- /**
10
- * @param {string} entityType - Type of entity (e.g., 'Skill', 'Behaviour')
11
- * @param {string} entityId - ID that was not found
12
- * @param {string} backPath - Path to navigate back to
13
- */
14
- constructor(entityType, entityId, backPath) {
15
- super(`${entityType} not found: ${entityId}`);
16
- this.name = "NotFoundError";
17
- this.entityType = entityType;
18
- this.entityId = entityId;
19
- this.backPath = backPath;
20
- }
21
- }
22
-
23
- /**
24
- * Invalid combination error (e.g., invalid job configuration)
25
- */
26
- export class InvalidCombinationError extends Error {
27
- /**
28
- * @param {string} message - Error message
29
- * @param {string} backPath - Path to navigate back to
30
- */
31
- constructor(message, backPath) {
32
- super(message);
33
- this.name = "InvalidCombinationError";
34
- this.backPath = backPath;
35
- }
36
- }
37
-
38
- /**
39
- * Data loading error
40
- */
41
- export class DataLoadError extends Error {
42
- /**
43
- * @param {string} message - Error message
44
- */
45
- constructor(message) {
46
- super(message);
47
- this.name = "DataLoadError";
48
- }
49
- }
7
+ export {
8
+ NotFoundError,
9
+ InvalidCombinationError,
10
+ DataLoadError,
11
+ } from "@forwardimpact/libui/errors";
@@ -5,7 +5,7 @@
5
5
  * Provides consistent key generation and get-or-create pattern.
6
6
  */
7
7
 
8
- import { deriveJob } from "@forwardimpact/libpathway/derivation";
8
+ import { deriveJob } from "@forwardimpact/libskill/derivation";
9
9
 
10
10
  /** @type {Map<string, Object>} */
11
11
  const cache = new Map();
@@ -13,22 +13,22 @@ const cache = new Map();
13
13
  /**
14
14
  * Build a consistent cache key from job parameters
15
15
  * @param {string} disciplineId
16
- * @param {string} gradeId
16
+ * @param {string} levelId
17
17
  * @param {string} [trackId] - Optional track ID
18
18
  * @returns {string}
19
19
  */
20
- export function buildJobKey(disciplineId, gradeId, trackId = null) {
20
+ export function buildJobKey(disciplineId, levelId, trackId = null) {
21
21
  if (trackId) {
22
- return `${disciplineId}_${gradeId}_${trackId}`;
22
+ return `${disciplineId}_${levelId}_${trackId}`;
23
23
  }
24
- return `${disciplineId}_${gradeId}`;
24
+ return `${disciplineId}_${levelId}`;
25
25
  }
26
26
 
27
27
  /**
28
28
  * Get or create a cached job definition
29
29
  * @param {Object} params
30
30
  * @param {Object} params.discipline
31
- * @param {Object} params.grade
31
+ * @param {Object} params.level
32
32
  * @param {Object} [params.track] - Optional track
33
33
  * @param {Array} params.skills
34
34
  * @param {Array} params.behaviours
@@ -37,18 +37,18 @@ export function buildJobKey(disciplineId, gradeId, trackId = null) {
37
37
  */
38
38
  export function getOrCreateJob({
39
39
  discipline,
40
- grade,
40
+ level,
41
41
  track = null,
42
42
  skills,
43
43
  behaviours,
44
44
  capabilities,
45
45
  }) {
46
- const key = buildJobKey(discipline.id, grade.id, track?.id);
46
+ const key = buildJobKey(discipline.id, level.id, track?.id);
47
47
 
48
48
  if (!cache.has(key)) {
49
49
  const job = deriveJob({
50
50
  discipline,
51
- grade,
51
+ level,
52
52
  track,
53
53
  skills,
54
54
  behaviours,
@@ -73,11 +73,11 @@ export function clearCache() {
73
73
  /**
74
74
  * Invalidate a specific job from the cache
75
75
  * @param {string} disciplineId
76
- * @param {string} gradeId
76
+ * @param {string} levelId
77
77
  * @param {string} [trackId] - Optional track ID
78
78
  */
79
- export function invalidateCachedJob(disciplineId, gradeId, trackId = null) {
80
- cache.delete(buildJobKey(disciplineId, gradeId, trackId));
79
+ export function invalidateCachedJob(disciplineId, levelId, trackId = null) {
80
+ cache.delete(buildJobKey(disciplineId, levelId, trackId));
81
81
  }
82
82
 
83
83
  /**
@@ -1,114 +1,7 @@
1
1
  /**
2
2
  * Simple Markdown to HTML converter
3
3
  *
4
- * Converts common markdown syntax to HTML. Designed for job descriptions
5
- * with headings, lists, bold text, and paragraphs.
4
+ * Re-exports from @forwardimpact/libui/markdown.
6
5
  */
7
6
 
8
- /**
9
- * Convert markdown text to HTML
10
- * @param {string} markdown - The markdown text to convert
11
- * @returns {string} HTML string
12
- */
13
- export function markdownToHtml(markdown) {
14
- const lines = markdown.split("\n");
15
- const htmlLines = [];
16
- let inList = false;
17
-
18
- for (let i = 0; i < lines.length; i++) {
19
- let line = lines[i];
20
-
21
- // Skip empty lines but close list if open
22
- if (line.trim() === "") {
23
- if (inList) {
24
- htmlLines.push("</ul>");
25
- inList = false;
26
- }
27
- continue;
28
- }
29
-
30
- // H1 heading
31
- if (line.startsWith("# ")) {
32
- if (inList) {
33
- htmlLines.push("</ul>");
34
- inList = false;
35
- }
36
- htmlLines.push(`<h1>${escapeHtml(line.slice(2))}</h1>`);
37
- continue;
38
- }
39
-
40
- // H2 heading
41
- if (line.startsWith("## ")) {
42
- if (inList) {
43
- htmlLines.push("</ul>");
44
- inList = false;
45
- }
46
- htmlLines.push(`<h2>${escapeHtml(line.slice(3))}</h2>`);
47
- continue;
48
- }
49
-
50
- // H3 heading
51
- if (line.startsWith("### ")) {
52
- if (inList) {
53
- htmlLines.push("</ul>");
54
- inList = false;
55
- }
56
- htmlLines.push(`<h3>${escapeHtml(line.slice(4))}</h3>`);
57
- continue;
58
- }
59
-
60
- // List item
61
- if (line.startsWith("- ")) {
62
- if (!inList) {
63
- htmlLines.push("<ul>");
64
- inList = true;
65
- }
66
- const content = formatInlineMarkdown(line.slice(2));
67
- htmlLines.push(`<li>${content}</li>`);
68
- continue;
69
- }
70
-
71
- // Regular paragraph
72
- if (inList) {
73
- htmlLines.push("</ul>");
74
- inList = false;
75
- }
76
- htmlLines.push(`<p>${formatInlineMarkdown(line)}</p>`);
77
- }
78
-
79
- // Close any open list
80
- if (inList) {
81
- htmlLines.push("</ul>");
82
- }
83
-
84
- return htmlLines.join("\n");
85
- }
86
-
87
- /**
88
- * Escape HTML special characters
89
- * @param {string} text - Text to escape
90
- * @returns {string} Escaped text
91
- */
92
- function escapeHtml(text) {
93
- return text
94
- .replace(/&/g, "&amp;")
95
- .replace(/</g, "&lt;")
96
- .replace(/>/g, "&gt;")
97
- .replace(/"/g, "&quot;")
98
- .replace(/'/g, "&#039;");
99
- }
100
-
101
- /**
102
- * Format inline markdown (bold text)
103
- * @param {string} text - Text to format
104
- * @returns {string} HTML formatted text
105
- */
106
- function formatInlineMarkdown(text) {
107
- // First escape HTML
108
- let result = escapeHtml(text);
109
-
110
- // Convert **bold** to <strong>bold</strong>
111
- result = result.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
112
-
113
- return result;
114
- }
7
+ export { markdownToHtml } from "@forwardimpact/libui/markdown";
@@ -1,77 +1,11 @@
1
1
  /**
2
2
  * Reactive state utilities for local component state
3
+ *
4
+ * Re-exports from @forwardimpact/libui/reactive.
3
5
  */
4
6
 
5
- /**
6
- * @template T
7
- * @typedef {Object} Reactive
8
- * @property {() => T} get - Get current value
9
- * @property {(value: T) => void} set - Set new value
10
- * @property {(fn: (prev: T) => T) => void} update - Update value with function
11
- * @property {(fn: (value: T) => void) => () => void} subscribe - Subscribe to changes
12
- */
13
-
14
- /**
15
- * Create a reactive state container
16
- * @template T
17
- * @param {T} initial - Initial value
18
- * @returns {Reactive<T>}
19
- */
20
- export function createReactive(initial) {
21
- let state = initial;
22
- const subscribers = new Set();
23
-
24
- return {
25
- get: () => state,
26
-
27
- set: (next) => {
28
- state = next;
29
- subscribers.forEach((fn) => fn(state));
30
- },
31
-
32
- update: (fn) => {
33
- state = fn(state);
34
- subscribers.forEach((sub) => sub(state));
35
- },
36
-
37
- subscribe: (fn) => {
38
- subscribers.add(fn);
39
- return () => subscribers.delete(fn);
40
- },
41
- };
42
- }
43
-
44
- /**
45
- * Create a computed value that updates when dependencies change
46
- * @template T
47
- * @param {() => T} compute - Computation function
48
- * @param {Reactive<*>[]} deps - Dependencies
49
- * @returns {Reactive<T>}
50
- */
51
- export function createComputed(compute, deps) {
52
- const computed = createReactive(compute());
53
-
54
- deps.forEach((dep) => {
55
- dep.subscribe(() => {
56
- computed.set(compute());
57
- });
58
- });
59
-
60
- return computed;
61
- }
62
-
63
- /**
64
- * Bind a reactive value to an element attribute
65
- * @template T
66
- * @param {Reactive<T>} reactive - Reactive state
67
- * @param {HTMLElement} element - Element to bind
68
- * @param {string} attribute - Attribute name
69
- * @param {(value: T) => *} [transform] - Transform function
70
- */
71
- export function bind(reactive, element, attribute, transform = (v) => v) {
72
- const update = (value) => {
73
- element[attribute] = transform(value);
74
- };
75
- update(reactive.get());
76
- reactive.subscribe(update);
77
- }
7
+ export {
8
+ createReactive,
9
+ createComputed,
10
+ bind,
11
+ } from "@forwardimpact/libui/reactive";