Skip to content

Commit 974463e

Browse files
authored
Merge pull request #6 from spivx/skip-chooses-default
fix: improve clarity in README and agents documentation regarding que…
2 parents d5f04de + 33d5d85 commit 974463e

File tree

16 files changed

+342
-141
lines changed

16 files changed

+342
-141
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ Build high-signal agent and instruction files from community-proven best practic
1313
1. Launch the app and switch from the landing hero to the Instructions Wizard.
1414
2. Pick the instruction file you want to assemble (from templates defined in `data/files.json`).
1515
3. Choose your framework and automatically load its follow-up question set (dynamic imports from `data/questions/<framework>.json`).
16-
4. Answer or skip topic prompts across general, architecture, performance, security, commits, and more.
17-
5. Review a completion summary that highlights which best practices made it into your file and which were skipped for later.
16+
4. Answer topic prompts across general, architecture, performance, security, commits, and more—or lean on the recommended defaults when you need a fast decision.
17+
5. Review a completion summary that highlights what made it into your file and which areas still need decisions.
1818

1919
## Community knowledge base
2020
- Every topic originates from the developer community—playbooks, real-world retrospectives, and shared tooling habits.
@@ -24,8 +24,8 @@ Build high-signal agent and instruction files from community-proven best practic
2424

2525
## Key interaction details
2626
- Tooltips open from the info icon, letting you explore examples, pros/cons, tags, and external docs without losing your place.
27-
- Multi-select questions support skipping (recorded as `null`) so uncertain topics never block progress.
28-
- Progress indicators keep a running count of answered versus skipped items, making gaps obvious before export.
27+
- Multi-select questions let you apply the curated default choice with a single click so momentum never stalls.
28+
- Progress indicators keep a running count of answered versus unanswered items, making gaps obvious before export.
2929

3030
## Run devcontext locally
3131
```bash

agents.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
- Framework selection (`data/frameworks.json`) with branching into framework-specific question sets (e.g., `data/questions/react.json`).
1212
- Dynamic question sets loaded via `import()` based on the chosen framework.
1313
- User actions per question:
14-
- Select single or multiple answers (with skip support that records `null`).
14+
- Select single or multiple answers, or apply the recommended default when unsure.
1515
- Review hover tooltips with examples, pros/cons, tags, and documentation links.
16-
- Complete flow with a summary of answered vs skipped items.
16+
- Complete flow with a summary of answered selections and remaining gaps.
1717

1818
## Data Conventions
1919
- Every answer object may define: `value`, `label`, `icon`, `example`, `infoLines` (derived from `pros`/`cons`), `tags`, `isDefault`, `disabled`, `disabledLabel`, and `docs`.

components/instructions-wizard.tsx

Lines changed: 83 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ import { buildStepFromQuestionSet, getFormatLabel, getMimeTypeForFormat, mapAnsw
2222
import type { GeneratedFileResult } from "@/types/output"
2323

2424
const fileOptions = filesData as FileOutputConfig[]
25-
const defaultFileOption = fileOptions.find((file) => file.enabled !== false) ?? fileOptions[0] ?? null
25+
const defaultFileOption =
26+
fileOptions.find((file) => file.isDefault) ??
27+
fileOptions.find((file) => file.enabled !== false) ??
28+
fileOptions[0] ??
29+
null
2630

2731
const FRAMEWORK_STEP_ID = "frameworks"
2832
const FRAMEWORK_QUESTION_ID = "frameworkSelection"
@@ -206,15 +210,14 @@ const frameworksStep: WizardStep = {
206210
{
207211
id: FRAMEWORK_QUESTION_ID,
208212
question: "Which framework are you working with?",
209-
skippable: false,
210213
answers: (rawFrameworks as FrameworkConfig[]).map((framework) => ({
211214
value: framework.id,
212215
label: framework.label,
213216
icon: framework.icon,
214217
disabled: framework.enabled === false,
215218
disabledLabel: framework.enabled === false ? "Soon" : undefined,
216219
docs: framework.docs,
217-
skippable: framework.skippable,
220+
isDefault: framework.isDefault,
218221
})),
219222
},
220223
],
@@ -311,6 +314,34 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
311314
return currentAnswerValue === value
312315
}
313316

317+
const defaultAnswer = useMemo(
318+
() => currentQuestion.answers.find((answer) => answer.isDefault),
319+
[currentQuestion]
320+
)
321+
322+
const isDefaultSelected = useMemo(() => {
323+
if (!defaultAnswer) {
324+
return false
325+
}
326+
327+
if (currentQuestion.allowMultiple) {
328+
return Array.isArray(currentAnswerValue) && currentAnswerValue.includes(defaultAnswer.value)
329+
}
330+
331+
return currentAnswerValue === defaultAnswer.value
332+
}, [currentAnswerValue, currentQuestion.allowMultiple, defaultAnswer])
333+
334+
const canUseDefault = Boolean(
335+
!isComplete &&
336+
defaultAnswer &&
337+
!defaultAnswer.disabled &&
338+
(!isDefaultSelected || currentQuestion.allowMultiple)
339+
)
340+
341+
const defaultButtonLabel = defaultAnswer
342+
? `Use default (${defaultAnswer.label})`
343+
: "Use default"
344+
314345
const advanceToNextQuestion = () => {
315346
const isLastQuestionInStep = currentQuestionIndex === currentStep.questions.length - 1
316347
const isLastStep = currentStepIndex === wizardSteps.length - 1
@@ -361,7 +392,7 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
361392
id: question.id,
362393
question: question.question,
363394
allowMultiple: question.allowMultiple,
364-
skippable: question.skippable,
395+
responseKey: question.responseKey,
365396
answers: question.answers.map(mapAnswerSourceToWizard),
366397
}))
367398

@@ -445,25 +476,33 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
445476
}
446477
}
447478

448-
const skipQuestion = () => {
449-
if (currentQuestion.skippable === false) {
479+
const applyDefaultAnswer = async () => {
480+
if (!defaultAnswer || defaultAnswer.disabled) {
450481
return
451482
}
452483

453484
setGeneratedFile(null)
454485

486+
const nextValue: Responses[keyof Responses] = currentQuestion.allowMultiple
487+
? [defaultAnswer.value]
488+
: defaultAnswer.value
489+
455490
setResponses((prev) => ({
456491
...prev,
457-
[currentQuestion.id]: null,
492+
[currentQuestion.id]: nextValue,
458493
}))
459494

460-
if (currentQuestion.id === FRAMEWORK_QUESTION_ID) {
461-
setDynamicSteps([])
495+
const isFrameworkQuestion = currentQuestion.id === FRAMEWORK_QUESTION_ID
496+
497+
if (isFrameworkQuestion) {
498+
await loadFrameworkQuestions(defaultAnswer.value, defaultAnswer.label)
499+
return
462500
}
463501

464-
advanceToNextQuestion()
502+
setTimeout(() => {
503+
advanceToNextQuestion()
504+
}, 0)
465505
}
466-
467506
const resetWizard = () => {
468507
setResponses({})
469508
setDynamicSteps([])
@@ -559,20 +598,26 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
559598

560599
wizardSteps.forEach((step) => {
561600
step.questions.forEach((question) => {
562-
const key = question.id
601+
const responseKey = question.responseKey ?? question.id
602+
603+
if (!(responseKey in questionsAndAnswers)) {
604+
return
605+
}
606+
563607
const answer = responses[question.id]
608+
const targetKey = responseKey as keyof WizardResponses
564609

565610
if (answer !== null && answer !== undefined) {
566611
if (question.allowMultiple && Array.isArray(answer)) {
567612
// For all other multi-selects, keep as array
568-
questionsAndAnswers[key as keyof WizardResponses] = Array.isArray(answer) ? answer.join(", ") : answer
613+
questionsAndAnswers[targetKey] = answer.join(", ")
569614
} else if (!question.allowMultiple && typeof answer === 'string') {
570-
questionsAndAnswers[key as keyof WizardResponses] = Array.isArray(answer) ? answer.join(", ") : answer
615+
questionsAndAnswers[targetKey] = answer
571616
} else {
572-
questionsAndAnswers[key as keyof WizardResponses] = Array.isArray(answer) ? answer.join(", ") : answer
617+
questionsAndAnswers[targetKey] = Array.isArray(answer) ? answer.join(", ") : (answer as string)
573618
}
574619
} else {
575-
questionsAndAnswers[key as keyof WizardResponses] = null
620+
questionsAndAnswers[targetKey] = null
576621
}
577622
})
578623
})
@@ -632,7 +677,7 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
632677
selectedFile
633678
? {
634679
question: "Instructions file",
635-
skipped: false,
680+
hasSelection: true,
636681
answers: [
637682
selectedFile.label,
638683
selectedFile.filename ? `Filename: ${selectedFile.filename}` : null,
@@ -641,14 +686,14 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
641686
}
642687
: {
643688
question: "Instructions file",
644-
skipped: true,
689+
hasSelection: false,
645690
answers: [],
646691
},
647692
...wizardSteps.flatMap((step) =>
648693
step.questions.map((question) => {
649694
const value = responses[question.id]
650695
const selectedAnswers = question.answers.filter((answer) => {
651-
if (value === null) {
696+
if (value === null || value === undefined) {
652697
return false
653698
}
654699

@@ -661,7 +706,7 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
661706

662707
return {
663708
question: question.question,
664-
skipped: value === null,
709+
hasSelection: selectedAnswers.length > 0,
665710
answers: selectedAnswers.map((answer) => answer.label),
666711
}
667712
})
@@ -684,14 +729,14 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
684729
className="rounded-2xl border border-border/70 bg-background/90 p-5"
685730
>
686731
<p className="text-sm font-medium text-muted-foreground">{entry.question}</p>
687-
{entry.skipped ? (
688-
<p className="mt-2 text-base font-semibold text-foreground">Skipped</p>
689-
) : (
732+
{entry.hasSelection ? (
690733
<ul className="mt-2 list-disc space-y-1 pl-5 text-sm text-foreground">
691734
{entry.answers.map((answer) => (
692735
<li key={answer}>{answer}</li>
693736
))}
694737
</ul>
738+
) : (
739+
<p className="mt-2 text-base font-semibold text-foreground">No selection</p>
695740
)}
696741
</div>
697742
))}
@@ -722,7 +767,17 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
722767

723768
const isAtFirstQuestion = currentStepIndex === 0 && currentQuestionIndex === 0
724769
const backDisabled = isAtFirstQuestion && !isComplete
725-
const canSkipCurrentQuestion = !isComplete && currentQuestion.skippable !== false
770+
const defaultButtonTitle = !canUseDefault
771+
? isComplete
772+
? "Questions complete"
773+
: defaultAnswer?.disabled
774+
? "Default option unavailable"
775+
: isDefaultSelected && !currentQuestion.allowMultiple
776+
? "Default already selected"
777+
: defaultAnswer
778+
? undefined
779+
: "No default available"
780+
: undefined
726781
const showChangeFile = Boolean(onClose && selectedFile)
727782

728783
const actionBar = (
@@ -738,17 +793,11 @@ export function InstructionsWizard({ onClose, selectedFileId }: InstructionsWiza
738793
</Button>
739794
<Button
740795
variant="ghost"
741-
onClick={skipQuestion}
742-
disabled={!canSkipCurrentQuestion}
743-
title={
744-
canSkipCurrentQuestion
745-
? undefined
746-
: isComplete
747-
? "Questions complete"
748-
: "This question must be answered"
749-
}
796+
onClick={() => void applyDefaultAnswer()}
797+
disabled={!canUseDefault}
798+
title={defaultButtonTitle}
750799
>
751-
Skip
800+
{defaultButtonLabel}
752801
</Button>
753802
</div>
754803
<div className="ml-auto flex flex-wrap justify-end gap-2">

data/architecture.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"value": "reactQuery",
88
"label": "React Query",
99
"example": "Use React Query for server state management",
10-
"docs": "https://tanstack.com/query/latest/docs/react/overview"
10+
"docs": "https://tanstack.com/query/latest/docs/react/overview",
11+
"isDefault": true
1112
},
1213
{
1314
"value": "reduxToolkit",
@@ -30,7 +31,8 @@
3031
"value": "hooks",
3132
"label": "Custom hooks",
3233
"example": "Encapsulate API calls in useFetchSomething()",
33-
"docs": "https://react.dev/learn/reusing-logic-with-custom-hooks"
34+
"docs": "https://react.dev/learn/reusing-logic-with-custom-hooks",
35+
"isDefault": true
3436
}
3537
]
3638
},
@@ -41,7 +43,8 @@
4143
{
4244
"value": "featureFolders",
4345
"label": "Feature-based folders",
44-
"example": "src/features/auth/LoginForm.tsx"
46+
"example": "src/features/auth/LoginForm.tsx",
47+
"isDefault": true
4548
},
4649
{
4750
"value": "domainDriven",

data/commits.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
"value": "conventional",
88
"label": "Conventional Commits",
99
"example": "feat(auth): add login endpoint",
10-
"docs": "https://www.conventionalcommits.org/en/v1.0.0/"
10+
"docs": "https://www.conventionalcommits.org/en/v1.0.0/",
11+
"isDefault": true
1112
},
1213
{
1314
"value": "gitmoji",
1415
"label": "Gitmoji",
15-
"example": " add login endpoint",
16+
"example": "\u2728 add login endpoint",
1617
"docs": "https://gitmoji.dev/"
1718
},
1819
{
@@ -34,7 +35,8 @@
3435
{
3536
"value": "reviewRequired",
3637
"label": "Require at least one review",
37-
"example": "PRs must be approved by another team member."
38+
"example": "PRs must be approved by another team member.",
39+
"isDefault": true
3840
},
3941
{
4042
"value": "changelog",

data/files.json

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"enabled": true,
88
"icon": "markdown",
99
"docs": "https://docs.github.com/en/copilot",
10-
"skippable": false
10+
"isDefault": true
1111
},
1212
{
1313
"id": "agents-md",
@@ -16,16 +16,14 @@
1616
"format": "markdown",
1717
"enabled": true,
1818
"icon": "markdown",
19-
"docs": "https://docs.github.com/en/copilot",
20-
"skippable": false
19+
"docs": "https://docs.github.com/en/copilot"
2120
},
2221
{
2322
"id": "cursor-rules",
2423
"label": "Cursor Rules",
2524
"filename": ".cursor/rules",
2625
"format": "json",
2726
"enabled": true,
28-
"docs": "https://docs.cursor.com/workflows/rules",
29-
"skippable": false
27+
"docs": "https://docs.cursor.com/workflows/rules"
3028
}
3129
]

data/frameworks.json

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,20 @@
55
"icon": "react",
66
"enabled": true,
77
"docs": "https://react.dev/learn",
8-
"skippable": false
8+
"isDefault": true
99
},
1010
{
1111
"id": "nextjs",
1212
"label": "Next.js",
1313
"icon": "nextdotjs",
1414
"enabled": true,
15-
"docs": "https://nextjs.org/docs",
16-
"skippable": false
15+
"docs": "https://nextjs.org/docs"
1716
},
1817
{
1918
"id": "angular",
2019
"label": "Angular",
2120
"icon": "angular",
2221
"enabled": true,
23-
"docs": "https://angular.io/docs",
24-
"skippable": false
22+
"docs": "https://angular.io/docs"
2523
}
2624
]

0 commit comments

Comments
 (0)