@vue-skuilder/cli 0.1.14-2 → 0.1.15

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 (43) hide show
  1. package/cypress/e2e/custom-questions-dev.cy.js +27 -0
  2. package/cypress/e2e/custom-questions-studio.cy.js +93 -0
  3. package/cypress/e2e/scaffolded-app.cy.js +32 -0
  4. package/cypress/support/commands.js +9 -0
  5. package/cypress/support/e2e.js +17 -0
  6. package/cypress.config.js +17 -0
  7. package/dist/commands/pack.d.ts.map +1 -1
  8. package/dist/commands/pack.js +13 -0
  9. package/dist/commands/pack.js.map +1 -1
  10. package/dist/commands/studio.js +52 -24
  11. package/dist/commands/studio.js.map +1 -1
  12. package/dist/mcp-server.js +9 -3
  13. package/dist/mcp-server.js.map +1 -1
  14. package/dist/standalone-ui-template/package.json +3 -3
  15. package/dist/standalone-ui-template/src/main.ts +11 -18
  16. package/dist/standalone-ui-template/src/questions/MultipleChoiceQuestion.ts +2 -3
  17. package/dist/standalone-ui-template/src/questions/MultipleChoiceQuestionView.vue +24 -10
  18. package/dist/standalone-ui-template/src/questions/NumberRangeQuestion.ts +2 -1
  19. package/dist/standalone-ui-template/src/questions/NumberRangeQuestionView.vue +19 -6
  20. package/dist/standalone-ui-template/src/questions/README.md +28 -14
  21. package/dist/standalone-ui-template/src/questions/SimpleTextQuestion.ts +2 -1
  22. package/dist/standalone-ui-template/src/questions/SimpleTextQuestionView.vue +20 -11
  23. package/dist/standalone-ui-template/src/questions/index.ts +9 -5
  24. package/dist/standalone-ui-template/src/views/StudyView.vue +1 -1
  25. package/dist/standalone-ui-template/vite.config.ts +4 -0
  26. package/dist/studio-ui-src/App.vue +4 -4
  27. package/dist/studio-ui-src/components/StudioFlush.vue +1 -1
  28. package/dist/studio-ui-src/main.ts +55 -20
  29. package/dist/studio-ui-src/package.json +2 -2
  30. package/dist/studio-ui-src/views/BrowseView.vue +3 -0
  31. package/dist/utils/pack-courses.d.ts.map +1 -1
  32. package/dist/utils/pack-courses.js +26 -0
  33. package/dist/utils/pack-courses.js.map +1 -1
  34. package/dist/utils/template.d.ts.map +1 -1
  35. package/dist/utils/template.js +35 -0
  36. package/dist/utils/template.js.map +1 -1
  37. package/eslint.config.mjs +1 -1
  38. package/package.json +21 -12
  39. package/src/commands/pack.ts +19 -0
  40. package/src/commands/studio.ts +35 -5
  41. package/src/mcp-server.ts +10 -3
  42. package/src/utils/pack-courses.ts +33 -0
  43. package/src/utils/template.ts +49 -0
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.14-2",
6
+ "version": "0.1.15",
7
7
  "type": "module",
8
8
  "main": "./dist-lib/questions.cjs.js",
9
9
  "module": "./dist-lib/questions.mjs",
@@ -51,12 +51,12 @@
51
51
  "@types/cypress": "1.1.6",
52
52
  "@types/events": "^3",
53
53
  "@vitejs/plugin-vue": "^5.2.1",
54
- "cypress": "14.1.0",
54
+ "cypress": "^15.6.0",
55
55
  "typescript": "^5.7.2",
56
56
  "vite": "^6.0.9",
57
57
  "vite-plugin-dts": "^4.3.0",
58
58
  "vue-tsc": "^1.8.0",
59
59
  "wait-on": "8.0.2"
60
60
  },
61
- "stableVersion": "0.1.13"
61
+ "stableVersion": "0.1.15"
62
62
  }
@@ -42,32 +42,25 @@ import config from '../skuilder.config.json';
42
42
  };
43
43
 
44
44
  if (config.dataLayerType === 'static') {
45
- // Load manifest for static mode
46
- const courseId = config.course;
47
- if (!courseId) {
48
- throw new Error('Course ID required for static data layer');
49
- }
50
-
45
+ // Load root skuilder.json manifest for static mode
51
46
  try {
52
- const manifestResponse = await fetch(`/static-courses/${courseId}/manifest.json`);
53
- if (!manifestResponse.ok) {
47
+ const rootManifestUrl = '/skuilder.json';
48
+ const rootManifestResponse = await fetch(rootManifestUrl);
49
+ if (!rootManifestResponse.ok) {
54
50
  throw new Error(
55
- `Failed to load manifest: ${manifestResponse.status} ${manifestResponse.statusText}`
51
+ `Failed to load root manifest: ${rootManifestResponse.status} ${rootManifestResponse.statusText}`
56
52
  );
57
53
  }
58
- const manifest = await manifestResponse.json();
59
- console.log(`Loaded manifest for course ${courseId}`);
60
- console.log(JSON.stringify(manifest));
54
+ const rootManifest = await rootManifestResponse.json();
55
+ console.log('[DEBUG] Loaded root manifest:', rootManifest);
61
56
 
62
57
  dataLayerOptions = {
63
- staticContentPath: '/static-courses',
64
- manifests: {
65
- [courseId]: manifest,
66
- },
58
+ rootManifest,
59
+ rootManifestUrl: new URL(rootManifestUrl, window.location.href).href,
67
60
  };
68
61
  } catch (error) {
69
- console.error('[DEBUG] Failed to load course manifest:', error);
70
- throw new Error(`Could not load course manifest for ${courseId}: ${error}`);
62
+ console.error('[DEBUG] Failed to load root manifest:', error);
63
+ throw new Error(`Could not load root skuilder.json manifest: ${error}`);
71
64
  }
72
65
  }
73
66
 
@@ -1,5 +1,6 @@
1
1
  import { ViewData, Answer, Question } from '@vue-skuilder/courseware';
2
2
  import { FieldType, DataShape, DataShapeName } from '@vue-skuilder/common';
3
+ import { markRaw } from 'vue';
3
4
  import MultipleChoiceQuestionView from './MultipleChoiceQuestionView.vue';
4
5
 
5
6
  export class MultipleChoiceQuestion extends Question {
@@ -14,9 +15,7 @@ export class MultipleChoiceQuestion extends Question {
14
15
  },
15
16
  ];
16
17
 
17
- public static views = [
18
- { name: 'MultipleChoiceQuestionView', component: MultipleChoiceQuestionView },
19
- ];
18
+ public static views = [markRaw(MultipleChoiceQuestionView)];
20
19
 
21
20
  // @ts-expect-error TS6133: Used in Vue template
22
21
  private _questionText: string;
@@ -10,34 +10,48 @@
10
10
  </template>
11
11
 
12
12
  <script setup lang="ts">
13
- import { ref, PropType } from 'vue';
13
+ import { ref, computed, PropType, defineOptions } from 'vue';
14
14
  import { useViewable, useQuestionView } from '@vue-skuilder/courseware';
15
15
  import { MultipleChoiceQuestion } from './MultipleChoiceQuestion';
16
16
  import { ViewData } from '@vue-skuilder/common';
17
17
 
18
+ defineOptions({
19
+ name: 'MultipleChoiceQuestionView'
20
+ });
21
+
18
22
  const props = defineProps({
19
- questionText: {
20
- type: String,
21
- required: true,
22
- },
23
- options: {
24
- type: Array as () => string[],
25
- required: true,
26
- },
27
23
  data: {
28
24
  type: Array as PropType<ViewData[]>,
29
25
  required: true,
30
26
  },
27
+ modifyDifficulty: {
28
+ type: Number,
29
+ required: false,
30
+ default: 0,
31
+ },
31
32
  });
32
33
 
34
+ const emit = defineEmits(['emit-response']);
35
+
33
36
  const selectedAnswer = ref('');
34
37
 
35
- const viewableUtils = useViewable(props, () => {}, 'MultipleChoiceQuestionView');
38
+ const viewableUtils = useViewable(props, emit, 'MultipleChoiceQuestionView');
36
39
  const questionUtils = useQuestionView<MultipleChoiceQuestion>(viewableUtils);
37
40
 
38
41
  // Initialize question
39
42
  questionUtils.question.value = new MultipleChoiceQuestion(props.data);
40
43
 
44
+ // Extract question text and options from the question instance
45
+ const questionText = computed(() => {
46
+ const question = new MultipleChoiceQuestion(props.data);
47
+ return (question as any)._questionText || '';
48
+ });
49
+
50
+ const options = computed(() => {
51
+ const question = new MultipleChoiceQuestion(props.data);
52
+ return (question as any).options || [];
53
+ });
54
+
41
55
  const submitAnswer = () => {
42
56
  if (selectedAnswer.value) {
43
57
  questionUtils.submitAnswer({ response: selectedAnswer.value });
@@ -1,5 +1,6 @@
1
1
  import { ViewData, Answer, Question } from '@vue-skuilder/courseware';
2
2
  import { FieldType, DataShape, DataShapeName } from '@vue-skuilder/common';
3
+ import { markRaw } from 'vue';
3
4
  import NumberRangeQuestionView from './NumberRangeQuestionView.vue';
4
5
 
5
6
  export class NumberRangeQuestion extends Question {
@@ -14,7 +15,7 @@ export class NumberRangeQuestion extends Question {
14
15
  },
15
16
  ];
16
17
 
17
- public static views = [{ name: 'NumberRangeQuestionView', component: NumberRangeQuestionView }];
18
+ public static views = [markRaw(NumberRangeQuestionView)];
18
19
 
19
20
  // @ts-expect-error TS6133: Used in Vue template
20
21
  private questionText: string;
@@ -7,30 +7,43 @@
7
7
  </template>
8
8
 
9
9
  <script setup lang="ts">
10
- import { ref, PropType } from 'vue';
10
+ import { ref, computed, PropType, defineOptions } from 'vue';
11
11
  import { useViewable, useQuestionView } from '@vue-skuilder/courseware';
12
12
  import { NumberRangeQuestion } from './NumberRangeQuestion';
13
13
  import { ViewData } from '@vue-skuilder/common';
14
14
 
15
+ defineOptions({
16
+ name: 'NumberRangeQuestionView'
17
+ });
18
+
15
19
  const props = defineProps({
16
- questionText: {
17
- type: String,
18
- required: true,
19
- },
20
20
  data: {
21
21
  type: Array as PropType<ViewData[]>,
22
22
  required: true,
23
23
  },
24
+ modifyDifficulty: {
25
+ type: Number,
26
+ required: false,
27
+ default: 0,
28
+ },
24
29
  });
25
30
 
31
+ const emit = defineEmits(['emit-response']);
32
+
26
33
  const userAnswer = ref<number | null>(null);
27
34
 
28
- const viewableUtils = useViewable(props, () => {}, 'NumberRangeQuestionView');
35
+ const viewableUtils = useViewable(props, emit, 'NumberRangeQuestionView');
29
36
  const questionUtils = useQuestionView<NumberRangeQuestion>(viewableUtils);
30
37
 
31
38
  // Initialize question
32
39
  questionUtils.question.value = new NumberRangeQuestion(props.data);
33
40
 
41
+ // Extract question text from the question instance
42
+ const questionText = computed(() => {
43
+ const question = new NumberRangeQuestion(props.data);
44
+ return (question as any).questionText || '';
45
+ });
46
+
34
47
  const submitAnswer = () => {
35
48
  if (userAnswer.value !== null) {
36
49
  questionUtils.submitAnswer({ response: userAnswer.value });
@@ -24,19 +24,24 @@ To use your custom questions in a course, you need to:
24
24
 
25
25
  ```typescript
26
26
  // MyCustomQuestion.ts
27
+ import { markRaw } from 'vue';
27
28
  import { Question, DataShape, ViewData, Answer } from '@vue-skuilder/courseware';
28
29
  import { FieldType } from '@vue-skuilder/common';
29
30
  import MyCustomQuestionView from './MyCustomQuestionView.vue';
30
31
 
31
32
  export class MyCustomQuestion extends Question {
32
33
  public static dataShapes: DataShape[] = [
33
- new DataShape('MyCustomQuestion', [
34
- { name: 'myField', type: FieldType.STRING },
35
- ]),
34
+ {
35
+ name: 'MyCustomQuestion' as DataShapeName,
36
+ fields: [
37
+ { name: 'myField', type: FieldType.STRING },
38
+ ],
39
+ },
36
40
  ];
37
41
 
42
+ // Direct inline view registration - use markRaw() to prevent Vue reactivity
38
43
  public static views = [
39
- { name: 'MyCustomQuestionView', component: MyCustomQuestionView },
44
+ { name: 'MyCustomQuestionView', component: markRaw(MyCustomQuestionView) },
40
45
  ];
41
46
 
42
47
  constructor(data: ViewData[]) {
@@ -59,6 +64,11 @@ To use your custom questions in a course, you need to:
59
64
  }
60
65
  ```
61
66
 
67
+ **Important Notes:**
68
+ - Use `markRaw()` to wrap component imports (prevents unnecessary reactivity)
69
+ - Views must be `{ name: string, component: ViewComponent }` format for studio mode
70
+ - The `name` field must match your component's `defineOptions({ name: '...' })`
71
+
62
72
  2. **Create Your Vue Component**: Create a Vue component (e.g., `MyCustomQuestionView.vue`) that will render your question and allow user interaction. This component will receive props based on the `ViewData` you define for your question.
63
73
 
64
74
  ```vue
@@ -72,9 +82,14 @@ To use your custom questions in a course, you need to:
72
82
  </template>
73
83
 
74
84
  <script setup lang="ts">
75
- import { ref } from 'vue';
85
+ import { ref, defineOptions } from 'vue';
76
86
  import { useStudySessionStore } from '@vue-skuilder/common-ui';
77
87
 
88
+ // REQUIRED: Component name for runtime lookup
89
+ defineOptions({
90
+ name: 'MyCustomQuestionView'
91
+ });
92
+
78
93
  const props = defineProps({
79
94
  // Define props based on your question's data
80
95
  questionData: { type: Object, required: true },
@@ -90,6 +105,8 @@ To use your custom questions in a course, you need to:
90
105
  </script>
91
106
  ```
92
107
 
108
+ **Important:** Component name must match Question class `views` array name
109
+
93
110
  3. **Register Your Question and Course**: In your application's entry point (e.g., `src/main.ts` or `src/App.vue`), you need to import your custom question and include it in a `Course` instance. Then, register this course with the `allCourses` list.
94
111
 
95
112
  ```typescript
@@ -117,13 +134,10 @@ To use your custom questions in a course, you need to:
117
134
 
118
135
  **Note**: The `allCourses` object is a singleton that manages all available courses and their associated questions and views. By adding your custom course to `allCourses.courses`, it becomes discoverable by the `CardViewer` and other components that rely on the course registry.
119
136
 
120
- ## Developing New Questions
121
-
122
- When developing new questions, consider the following:
123
-
124
- - **DataShape Definition**: Carefully define the `DataShape` for your question. This dictates the structure of the data that will be passed to your question's constructor and Vue component.
125
- - **Answer Evaluation**: Implement the `isCorrect` method in your `Question` subclass to define how user answers are evaluated.
126
- - **Vue Component Props**: Ensure your Vue component's `props` match the data fields defined in your `DataShape` and any additional data you pass from your `Question` instance.
127
- - **StudySessionStore**: Use the `useStudySessionStore()` composable from `@vue-skuilder/common-ui` to submit user answers and interact with the study session logic.
137
+ ## Key Requirements
128
138
 
129
- Feel free to modify and extend the provided examples to suit your needs.
139
+ - **DataShape Definition**: Defines data structure passed to constructor and Vue component
140
+ - **Answer Evaluation**: Implement `isCorrect()` method in your Question subclass
141
+ - **Component Names**: Use `defineOptions({ name: '...' })` - must match Question class `views` array
142
+ - **View Registration**: Register views inline with `markRaw()` - no separate setup files needed
143
+ - **Format**: Use `{ name: string, component: ViewComponent }` format for studio mode compatibility
@@ -1,5 +1,6 @@
1
1
  import { ViewData, Answer, Question } from '@vue-skuilder/courseware';
2
2
  import { FieldType, DataShape, DataShapeName } from '@vue-skuilder/common';
3
+ import { markRaw } from 'vue';
3
4
  import SimpleTextQuestionView from './SimpleTextQuestionView.vue';
4
5
 
5
6
  export class SimpleTextQuestion extends Question {
@@ -13,7 +14,7 @@ export class SimpleTextQuestion extends Question {
13
14
  },
14
15
  ];
15
16
 
16
- public static views = [{ name: 'SimpleTextQuestionView', component: SimpleTextQuestionView }];
17
+ public static views = [markRaw(SimpleTextQuestionView)];
17
18
 
18
19
  // @ts-expect-error TS6133: Used in Vue template
19
20
  private questionText: string;
@@ -1,5 +1,6 @@
1
1
  <template>
2
2
  <div>
3
+ <p>HELLO WORLD</p>
3
4
  <p>{{ questionText }}</p>
4
5
  <input v-model="userAnswer" @keyup.enter="submitAnswer" placeholder="Your answer" />
5
6
  <button @click="submitAnswer">Submit</button>
@@ -7,38 +8,46 @@
7
8
  </template>
8
9
 
9
10
  <script setup lang="ts">
10
- import { ref, onMounted, PropType } from 'vue';
11
+ import { ref, computed, PropType, defineOptions } from 'vue';
11
12
  import { useViewable, useQuestionView } from '@vue-skuilder/courseware';
12
13
  import { SimpleTextQuestion } from './SimpleTextQuestion';
13
14
  import { ViewData } from '@vue-skuilder/common';
14
15
 
16
+ defineOptions({
17
+ name: 'SimpleTextQuestionView',
18
+ });
19
+
15
20
  const props = defineProps({
16
- questionText: {
17
- type: String,
18
- required: true,
19
- },
20
21
  data: {
21
22
  type: Array as PropType<ViewData[]>,
22
23
  required: true,
23
24
  },
25
+ modifyDifficulty: {
26
+ type: Number,
27
+ required: false,
28
+ default: 0,
29
+ },
24
30
  });
25
31
 
32
+ const emit = defineEmits(['emit-response']);
33
+
26
34
  const userAnswer = ref('');
27
35
 
28
- const viewableUtils = useViewable(props, () => {}, 'SimpleTextQuestionView');
36
+ const viewableUtils = useViewable(props, emit, 'SimpleTextQuestionView');
29
37
  const questionUtils = useQuestionView<SimpleTextQuestion>(viewableUtils);
30
38
 
31
39
  // Initialize question
32
40
  questionUtils.question.value = new SimpleTextQuestion(props.data);
33
41
 
42
+ // Extract question text from the question instance
43
+ const questionText = computed(() => {
44
+ const question = new SimpleTextQuestion(props.data);
45
+ return (question as any).questionText || '';
46
+ });
47
+
34
48
  const submitAnswer = () => {
35
49
  questionUtils.submitAnswer({ response: userAnswer.value });
36
50
  };
37
-
38
- onMounted(() => {
39
- // Optionally, you might want to focus the input field on mount
40
- // This requires a ref on the input element
41
- });
42
51
  </script>
43
52
 
44
53
  <style scoped>
@@ -52,13 +52,17 @@ export function allCustomQuestions() {
52
52
  });
53
53
 
54
54
  // Collect all view components from questions
55
- const views: ViewComponent[] = [];
55
+ const views: Array<{ name: string; component: ViewComponent }> = [];
56
56
  questionClasses.forEach((questionClass) => {
57
57
  if (questionClass.views) {
58
58
  questionClass.views.forEach((view) => {
59
- // Avoid duplicates by name
60
- if (!views.find((existing) => existing.name === view.name)) {
61
- views.push(view);
59
+ const viewName = (view as any).name || (view as any).__name;
60
+ if (viewName) {
61
+ if (!views.find((existing) => existing.name === viewName)) {
62
+ views.push({ name: viewName, component: view });
63
+ }
64
+ } else {
65
+ console.warn('[allCustomQuestions] View component missing name property:', view);
62
66
  }
63
67
  });
64
68
  }
@@ -102,7 +106,7 @@ export interface CustomQuestionsExport {
102
106
  typeof SimpleTextQuestion | typeof MultipleChoiceQuestion | typeof NumberRangeQuestion
103
107
  >;
104
108
  dataShapes: DataShape[];
105
- views: ViewComponent[];
109
+ views: Array<{ name: string; component: ViewComponent }>;
106
110
  meta: {
107
111
  questionCount: number;
108
112
  dataShapeCount: number;
@@ -35,7 +35,7 @@ const user = getDataLayer().getUserDB();
35
35
  const dataLayer = getDataLayer();
36
36
  const configStore = useConfigStore();
37
37
  const sessionPrepared = ref(false);
38
- const sessionTimeLimit = ref(configStore.config.sessionTimeLimit);
38
+ const sessionTimeLimit = ref(Number(configStore.config.sessionTimeLimit));
39
39
  const sessionContentSources = ref<ContentSourceID[]>([]);
40
40
  const studySessionConfig = ref<StudySessionConfig>({
41
41
  likesConfetti: configStore.config.likesConfetti,
@@ -47,6 +47,10 @@ export default defineConfig({
47
47
  minify: 'terser',
48
48
  terserOptions: {
49
49
  keep_classnames: true,
50
+ keep_fnames: true,
51
+ mangle: {
52
+ properties: false,
53
+ },
50
54
  },
51
55
  lib: {
52
56
  entry: resolve(__dirname, 'src/questions/index.ts'),
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <v-app>
3
- <v-app-bar color="primary">
3
+ <v-app-bar>
4
4
  <v-toolbar-title>
5
5
  <v-icon start>mdi-palette</v-icon>
6
6
  Skuilder Studio
@@ -9,19 +9,19 @@
9
9
 
10
10
  <!-- Navigation buttons -->
11
11
  <v-btn-group v-if="courseId" class="mr-4">
12
- <v-btn :color="$route.name === 'browse' ? 'secondary' : 'transparent'" @click="$router.push('/')">
12
+ <v-btn :color="$route.name === 'browse' ? 'primary' : 'secondary'" @click="$router.push('/')">
13
13
  <v-icon start>mdi-magnify</v-icon>
14
14
  Browse Course
15
15
  </v-btn>
16
16
  <v-btn
17
- :color="$route.name === 'create-card' ? 'secondary' : 'transparent'"
17
+ :color="$route.name === 'create-card' ? 'primary' : 'secondary'"
18
18
  @click="$router.push('/create-card')"
19
19
  >
20
20
  <v-icon start>mdi-card-plus</v-icon>
21
21
  Create Card
22
22
  </v-btn>
23
23
  <v-btn
24
- :color="$route.name === 'bulk-import' ? 'secondary' : 'transparent'"
24
+ :color="$route.name === 'bulk-import' ? 'primary' : 'secondary'"
25
25
  @click="$router.push('/bulk-import')"
26
26
  >
27
27
  <v-icon start>mdi-file-import</v-icon>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <v-btn color="success" :loading="flushing" :disabled="flushing" @click="handleFlush">
2
+ <v-btn :loading="flushing" :disabled="flushing" @click="handleFlush">
3
3
  <v-icon start>mdi-content-save</v-icon>
4
4
  Flush to Static
5
5
  </v-btn>
@@ -68,16 +68,25 @@ const vuetify = createVuetify({
68
68
  try {
69
69
  console.log(' 📁 Fetching custom-questions-config.json');
70
70
  const configResponse = await fetch('/custom-questions-config.json');
71
+
72
+ console.log(` 🔍 Config fetch response status: ${configResponse.status}`);
73
+ console.log(` 🔍 Config fetch response URL: ${configResponse.url}`);
74
+
71
75
  if (configResponse.ok) {
72
76
  console.log(' ✅ Custom questions config file found');
73
77
  const customConfig = await configResponse.json();
74
78
  console.log(' 📋 Custom config parsed:', customConfig);
79
+
75
80
  if (customConfig.hasCustomQuestions && customConfig.importPath) {
76
81
  console.log(`🎨 Studio Mode: Loading custom questions from ${customConfig.packageName}`);
77
82
  console.log(` đŸ“Ļ Import path: ${customConfig.importPath}`);
83
+
78
84
  try {
79
- const customModule = await import(customConfig.importPath);
85
+ console.log(' 🔄 Attempting dynamic import...');
86
+ const customModule = await import(/* @vite-ignore */ customConfig.importPath);
80
87
  console.log(' ✅ Custom module imported successfully');
88
+ console.log(' 🔍 Module exports:', Object.keys(customModule));
89
+
81
90
  customQuestions = customModule.allCustomQuestions?.();
82
91
  if (customQuestions) {
83
92
  console.log(
@@ -85,24 +94,27 @@ const vuetify = createVuetify({
85
94
  );
86
95
  console.log(' 📊 Custom questions object:', customQuestions);
87
96
  } else {
88
- console.log(' âš ī¸ Custom module did not return questions data');
97
+ console.error(' ❌ FATAL: Custom module did not return questions data!');
98
+ console.error(' 🔍 allCustomQuestions result:', customQuestions);
99
+ console.error(' 🔍 allCustomQuestions function:', customModule.allCustomQuestions);
89
100
  }
90
101
  } catch (importError) {
91
- console.warn(
92
- ` âš ī¸ Failed to import custom questions: ${importError instanceof Error ? importError.message : String(importError)}`
93
- );
102
+ console.error(' ❌ FATAL: Failed to import custom questions module!');
103
+ console.error(' 🔍 Import path attempted:', customConfig.importPath);
104
+ console.error(' 🔍 Error:', importError);
105
+ throw importError; // Re-throw to make it visible
94
106
  }
95
107
  } else {
96
- console.log(
97
- ' â„šī¸ Custom config exists but hasCustomQuestions is false or importPath is missing'
98
- );
108
+ console.warn(' âš ī¸ Config exists but hasCustomQuestions is false or importPath is missing');
109
+ console.warn(' 🔍 Config content:', customConfig);
99
110
  }
100
111
  } else {
101
- console.log(' â„šī¸ Custom questions config file not found (this is normal)');
112
+ console.warn(` âš ī¸ Custom questions config not found (HTTP ${configResponse.status})`);
113
+ console.warn(` 🔍 Attempted URL: ${configResponse.url}`);
102
114
  }
103
115
  } catch (configError) {
104
- // No custom questions config - this is normal for default studio mode
105
- console.log(' â„šī¸ No custom questions config available (default studio mode)');
116
+ console.error(' ❌ Error fetching custom questions config:', configError);
117
+ // Don't throw - missing config is valid for non-custom courses
106
118
  }
107
119
 
108
120
  // Register custom question types in CourseConfig if available
@@ -164,17 +176,29 @@ const vuetify = createVuetify({
164
176
  );
165
177
  }
166
178
 
167
- // Build custom courseware registry
168
- const { allCourseWare, AllCourseWare } = await import('@vue-skuilder/courseware');
169
- const studioCourseWare = customQuestions
170
- ? new AllCourseWare([...allCourseWare.courses, ...customQuestions.courses])
171
- : allCourseWare;
172
-
173
- // Store custom courseware for use in components
174
- app.provide('studioCourseWare', studioCourseWare);
179
+ // Register custom courses with the allCourseWare singleton
180
+ console.log('🎨 Studio Mode: Registering custom courses');
181
+ const { allCourseWare } = await import('@vue-skuilder/courseware');
182
+ console.log(` 🔍 allCourseWare instance:`, allCourseWare);
183
+ console.log(` 🔍 Current courses BEFORE registration:`, allCourseWare.courses.map(c => c.name));
184
+
185
+ if (customQuestions?.courses) {
186
+ console.log(` đŸ“Ļ Registering ${customQuestions.courses.length} custom course(s)`);
187
+ console.log(` đŸ“Ļ Custom courses to register:`, customQuestions.courses.map(c => c.name));
188
+ customQuestions.courses.forEach((course) => {
189
+ // Check if already registered to avoid duplicates
190
+ if (!allCourseWare.courses.find((c) => c.name === course.name)) {
191
+ allCourseWare.courses.push(course);
192
+ console.log(` ✅ Registered course: ${course.name}`);
193
+ } else {
194
+ console.log(` â„šī¸ Course ${course.name} already registered`);
195
+ }
196
+ });
197
+ console.log(` 🔍 Current courses AFTER registration:`, allCourseWare.courses.map(c => c.name));
198
+ }
175
199
 
176
200
  console.log('🎨 Studio Mode: Collecting view components');
177
- const viewComponents = studioCourseWare.allViewsRaw();
201
+ const viewComponents = allCourseWare.allViewsRaw();
178
202
  console.log(` ✅ Collected ${Object.keys(viewComponents).length} base view components`);
179
203
 
180
204
  // Add custom question view components if available
@@ -199,6 +223,17 @@ const vuetify = createVuetify({
199
223
  app.component(name, component);
200
224
  });
201
225
 
226
+ // Provide inline markdown components if available
227
+ console.log('🎨 Studio Mode: Checking for inline markdown components');
228
+ if (customQuestions?.inlineComponents) {
229
+ console.log(` ✅ Found ${Object.keys(customQuestions.inlineComponents).length} inline components`);
230
+ console.log(` 📋 Component names: ${Object.keys(customQuestions.inlineComponents).join(', ')}`);
231
+ app.provide('markdownComponents', customQuestions.inlineComponents);
232
+ } else {
233
+ console.log(' â„šī¸ No inline markdown components available');
234
+ app.provide('markdownComponents', {});
235
+ }
236
+
202
237
  app.use(pinia);
203
238
  app.use(vuetify);
204
239
  app.use(router);
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.14-2",
6
+ "version": "0.1.15",
7
7
  "type": "module",
8
8
  "scripts": {
9
9
  "dev": "vite",
@@ -32,5 +32,5 @@
32
32
  "vite": "^6.0.9",
33
33
  "vue-tsc": "^1.8.0"
34
34
  },
35
- "stableVersion": "0.1.13"
35
+ "stableVersion": "0.1.15"
36
36
  }
@@ -38,6 +38,9 @@ const courseId = ref<string | null>(null);
38
38
 
39
39
  // View lookup function with proper context binding
40
40
  const viewLookupFunction = (viewDescription: any) => {
41
+ console.log('[BrowseView] Looking up view:', viewDescription);
42
+ console.log('[BrowseView] allCourseWare instance:', allCourseWare);
43
+ console.log('[BrowseView] Available courses:', allCourseWare.courses.map(c => c.name));
41
44
  return allCourseWare.getView(viewDescription);
42
45
  };
43
46
 
@@ -1 +1 @@
1
- {"version":3,"file":"pack-courses.d.ts","sourceRoot":"","sources":["../../src/utils/pack-courses.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoC5E"}
1
+ {"version":3,"file":"pack-courses.d.ts","sourceRoot":"","sources":["../../src/utils/pack-courses.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoE5E"}