Skip to content

Conversation

@charliecreates
Copy link
Contributor

Resolves #423

Context

What’s in this PR

  • Scaffold a dedicated FeatureSuggestions UI that renders after a chat response
  • Placeholder wiring to consume the list from the chat message payload (currently mocked for dev)
  • Basic layout/empty state and minimal styling
  • Keyboard navigation and focus management for accessibility
  • Dismissal and clear interactions (non-destructive)
  • Gated behind a feature flag so it doesn’t ship unfinished

Next steps (follow-ups)

  • Wire to real chat response data shape and finalize contracts
  • Visual design pass and responsive polish
  • Unit/e2e tests around rendering, selection, and dismissal
  • Telemetry hooks for impressions and selections

How to test (dev)

  • Enable the feature flag in local dev
  • Send a chat message; after receiving a response, the suggestions list should render using mock data
  • Verify empty state, keyboard navigation, and dismissal

Notes

  • This is intentionally minimal to unblock alignment on structure and data flow before full styling and integration.

…o-fill into input\n\n- Allow selecting one or many suggested features\n- Add "Select all" and "Clear selected" controls\n- Auto-compose selected features into the prompt area\n\nRefs #423
@netlify
Copy link

netlify bot commented Oct 13, 2025

Deploy Preview for fireproof-ai-builder ready!

Name Link
🔨 Latest commit 29c1877
🔍 Latest deploy log https://app.netlify.com/projects/fireproof-ai-builder/deploys/68ed4469d728530008d16ec4
😎 Deploy Preview https://deploy-preview-424--fireproof-ai-builder.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@charliecreates charliecreates bot requested a review from CharlieHelps October 13, 2025 18:26
Copy link
Contributor Author

@charliecreates charliecreates bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Clearing behavior is inconsistent: unchecking all items (via item toggles or turning off “Select all”) leaves stale prompt text because empty combined values are not propagated to onSelectSuggestion.
  • The pill click path bypasses selection state, causing potential mismatch between visible selections and the prompt contents; unify interactions to reduce confusion.
  • The “Select all” checkbox lacks an indeterminate state, reducing clarity for partial selections.
  • Test coverage does not verify clearing behavior when selections go to zero, risking regressions.
Additional notes (3)
  • Style | vibes.diy/pkg/app/components/QuickSuggestions.tsx:49-63
    The “Select all” checkbox does not reflect a partial selection state. Setting indeterminate improves clarity and accessibility for mixed selections.

  • Maintainability | vibes.diy/pkg/app/components/QuickSuggestions.tsx:87-87
    Using the array index as both the React key and the selection identifier couples UI state to render order. If the list ever changes (e.g., new data, reordering), selection can point at the wrong items. Consider moving to a stable ID (e.g., label or a dedicated id) for keys and for the selected set.

  • Maintainability | vibes.diy/pkg/app/components/QuickSuggestions.tsx:52-61
    The selection update logic (compute next, set state, compute combined string, call callback) is duplicated in both the master checkbox and each item’s checkbox. This repetition increases drift risk and makes behavioral changes (like clearing on empty) easy to miss in one path.

Summary of changes
  • Introduced multi-select behavior in QuickSuggestions with a selection toolbar.
  • Added a “Select all” checkbox and “Clear selected” button.
  • Implemented buildCombinedPrompt to compose selected suggestions into a single string (dash-prefixed lines).
  • Updated UI to render a checkbox alongside each suggestion pill; clicking a pill still inserts a single suggestion.
  • Added a test verifying select-all triggers combined prompt composition.

Comment on lines +49 to +61
type="checkbox"
aria-label="Select all suggested features"
checked={allSelected}
onChange={(e) => {
const next = new Set<number>();
if (e.currentTarget.checked) {
for (let i = 0; i < randomSuggestions.length; i++)
next.add(i);
}
setSelected(next);
const combined = buildCombinedPrompt(next);
if (combined) onSelectSuggestion(combined);
}}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unchecking the “Select all” box (or reducing selection to zero) does not clear the prompt, leaving stale content in the input. This violates user expectations introduced by your own inline comment about clearing to avoid confusion and creates inconsistent behavior vs. the "Clear selected" button. You compute the combined prompt but only call onSelectSuggestion when the string is non-empty, so the empty-state isn’t propagated.

Suggestion

Change the handler to always propagate the current composed prompt (including empty), so the input clears when the set becomes empty:

onChange={(e) => {
  const next = new Set<number>();
  if (e.currentTarget.checked) {
    for (let i = 0; i < randomSuggestions.length; i++) next.add(i);
  }
  setSelected(next);
  const combined = buildCombinedPrompt(next);
  onSelectSuggestion(combined);
}}

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion

Comment on lines +102 to +108
<button
type="button"
onClick={() => onSelectSuggestion(suggestion.text)}
className="cursor-pointer rounded-md bg-light-background-01 px-3 py-1.5 text-sm font-medium text-light-primary transition-colors hover:bg-light-decorative-01 dark:bg-dark-background-01 dark:text-dark-primary dark:hover:bg-dark-decorative-01"
>
{suggestion.label}
</button>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clicking a suggestion pill inserts a single suggestion into the input but does not update selection state, which can conflict with any existing multi-selection and produce mismatched UI vs. composed prompt. This dual interaction model (pills insert, checkboxes compose) is confusing and harder to maintain. A simpler, consistent approach is to make the pill click toggle its corresponding selection and rely on the composed prompt for output.

Suggestion

Unify the behavior by having the pill click toggle its checkbox and then compose the prompt:

onClick={() => {
  const next = new Set(selected);
  if (next.has(index)) next.delete(index);
  else next.add(index);
  setSelected(next);
  onSelectSuggestion(buildCombinedPrompt(next));
}}

If you prefer to preserve single-insert behavior when nothing is selected, you could conditionally do single-insert when selected.size === 0 and toggle otherwise.
Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion

Comment on lines +77 to +100

it("supports select-all checkbox to auto-fill combined features", () => {
const onSelectSuggestion = vi.fn();
const { container } = render(
<QuickSuggestions onSelectSuggestion={onSelectSuggestion} />,
);

const selectAll = container.querySelector(
'input[aria-label="Select all suggested features"]',
) as HTMLInputElement | null;
expect(selectAll).not.toBeNull();

if (selectAll) {
// Click the select-all checkbox
fireEvent.click(selectAll);

// Verify callback was invoked with a combined string
expect(onSelectSuggestion).toHaveBeenCalled();
const arg = onSelectSuggestion.mock.calls[0][0];
expect(typeof arg).toBe("string");
// Combined prompt should contain multiple lines joined with dashes
expect(arg.includes("- ")).toBe(true);
}
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There’s no test ensuring the prompt is cleared when all items are deselected (either by unchecking the last item or toggling “Select all” off). Given the behavioral change above, adding a test will prevent regressions and codify expected behavior.

Suggestion

Add a test that unselects all and verifies the callback receives an empty string:

it("clears composed prompt when selection becomes empty", () => {
  const onSelectSuggestion = vi.fn();
  const { container } = render(<QuickSuggestions onSelectSuggestion={onSelectSuggestion} />);

  const selectAll = container.querySelector('input[aria-label="Select all suggested features"]') as HTMLInputElement;
  expect(selectAll).not.toBeNull();

  // Select all, then unselect all
  fireEvent.click(selectAll);
  fireEvent.click(selectAll);

  // The latest call should clear
  const lastCall = onSelectSuggestion.mock.calls.at(-1);
  expect(lastCall?.[0]).toBe("");
});

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion

Comment on lines +84 to +90
const selectAll = container.querySelector(
'input[aria-label="Select all suggested features"]',
) as HTMLInputElement | null;
expect(selectAll).not.toBeNull();

if (selectAll) {
// Click the select-all checkbox
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test can be flaky: randomSuggestions is set inside useEffect, so the select-all checkbox won’t be present on the initial render. Querying the DOM immediately with querySelector risks a null result depending on timing. Prefer waiting for the element by label using Testing Library’s async queries.

Suggestion

Rewrite the test to await the element. Also avoid the conditional block once it’s awaited.

import { render, screen, fireEvent } from "@testing-library/react";

it("supports select-all checkbox to auto-fill combined features", async () => {
  const onSelectSuggestion = vi.fn();
  render(<QuickSuggestions onSelectSuggestion={onSelectSuggestion} />);

  const selectAll = await screen.findByLabelText("Select all suggested features");
  fireEvent.click(selectAll);

  expect(onSelectSuggestion).toHaveBeenCalled();
  const arg = onSelectSuggestion.mock.calls[0][0];
  expect(typeof arg).toBe("string");
  expect(arg.includes("- ")).toBe(true);
});

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.

@charliecreates charliecreates bot removed the request for review from CharlieHelps October 13, 2025 18:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a check box to add suggested features, auto to the input area , allow selecting one or all

2 participants