feat: add three viewing modes for app specification#566
Conversation
Introduces View, Edit, and Source modes for the spec page: - View: Clean read-only display with cards, badges, and accordions - Edit: Structured form-based editor for all spec fields - Source: Raw XML editor for advanced users Also adds @automaker/spec-parser shared package for XML parsing between server and client.
- Replace array index keys with stable UUIDs in array-field-editor, features-section, and roadmap-section components - Replace regex-based XML parsing with fast-xml-parser for robustness - Simplify renderContent logic in spec-view by removing dead code paths
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (3)
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughThis PR introduces a new XML spec parser library ( Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant SpecView
participant Parser as useSpecParser
participant Renderer as renderContent
participant ViewMode as SpecViewMode
participant EditMode as SpecEditMode
participant Tabs as SpecModeTabs
User->>SpecView: Load spec (XML content)
SpecView->>Parser: Parse XML via useSpecParser
Parser-->>SpecView: isParseValid, parsedSpec, errors
alt Parse Invalid
SpecView->>Tabs: Show error state
Tabs-->>User: Display error banner + disabled View/Edit tabs
User->>Tabs: Click source tab
Tabs->>SpecView: onModeChange('source')
SpecView->>Renderer: renderContent with source mode
Renderer->>User: Show XML editor
else Parse Valid
SpecView->>Tabs: Enable all tabs
Tabs-->>User: Show View/Edit/Source tabs
User->>Tabs: Click Edit tab
Tabs->>SpecView: onModeChange('edit')
SpecView->>Renderer: renderContent with edit mode
Renderer->>EditMode: Pass parsed spec
EditMode->>User: Render form sections
User->>EditMode: Edit field (e.g., project name)
EditMode->>EditMode: Update formData, convert to XML via specToXml
EditMode->>SpecView: onChange(newXML)
SpecView->>Parser: Re-parse updated XML
Parser-->>SpecView: Return validation result
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @stefandevo, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the user experience of the application specification page by introducing multiple viewing and editing modes. It abstracts the complexity of raw XML, offering user-friendly interfaces for both consumption and modification of project specifications. The changes are underpinned by a new, robust XML parsing and generation library, ensuring data consistency and validation across different representations of the specification. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a significant enhancement to the app specification page by adding three viewing modes: a read-only 'View' mode, a form-based 'Edit' mode, and the raw 'Source' XML editor. This greatly improves the user experience for both viewing and editing the specification. The introduction of the new @automaker/spec-parser shared package is a great architectural improvement that centralizes XML parsing and generation logic. The new components for the edit mode are well-structured and make editing the spec much more intuitive.
My review includes a few suggestions to improve the user experience and code robustness, such as adjusting the visibility of the save button based on the current mode and making the data handling in the view rendering more defensive. Overall, this is a solid feature implementation.
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@apps/ui/src/components/views/spec-view.tsx`:
- Line 2: Remove the unused RefreshCw import from the top-level import statement
in spec-view.tsx: locate the import "import { RefreshCw } from 'lucide-react';"
(or a combined import that includes RefreshCw) and delete the RefreshCw
identifier (or the entire import line if it contains only RefreshCw) so there
are no unused icon imports remaining.
In
`@apps/ui/src/components/views/spec-view/components/edit-mode/roadmap-section.tsx`:
- Around line 1-16: The file imports the Lucide icon named Map which shadows the
global Map and triggers noShadowRestrictedNames; update the import to alias the
icon (e.g., import { Map as MapIcon } from 'lucide-react') and then replace all
occurrences of the Map identifier in this module with the aliased name
(MapIcon); also update any other import sites in this file that import Map (the
other Map import occurrences) to use the alias to avoid the shadowing.
In `@apps/ui/src/components/views/spec-view/components/spec-view-mode.tsx`:
- Line 20: The import "Map" in spec-view-mode.tsx shadows the global Map
constructor; change the import to an alias (e.g., import { Map as MapIcon }) and
update all JSX usages (for example the component rendered around line with the
Map usage) to use <MapIcon ... /> instead of <Map ... />; ensure any props
passed to the original Map component remain unchanged and update any other
references in this file to the new alias.
In `@libs/spec-parser/src/xml-utils.ts`:
- Around line 41-44: The regex built in extractXmlSection interpolates raw
tagName into new RegExp which allows regex metacharacters to break or alter the
pattern; fix by escaping tagName before constructing the RegExp (e.g. add a
small utility like escapeRegex or use tagName.replace(/[-\/\\^$*+?.()|[\]{}]/g,
'\\$&') and then use the escaped value in new RegExp), and apply the same escape
to the other similar function in the block around lines 54-61 so both places use
the escaped tagName when building their RegExp.
🧹 Nitpick comments (10)
apps/ui/src/components/layout/project-switcher/components/project-switcher-item.tsx (1)
40-47: Harden test-id slugging to avoid selector escapes and collisions.Whitespace-only replacement leaves special characters intact and can produce duplicate IDs for same-named projects. Consider a stricter slug and a fallback to
project.idwhen empty/duplicated.♻️ Suggested slugging refinement
- // Create a sanitized project name for test ID (lowercase, hyphens instead of spaces) - const sanitizedName = project.name.toLowerCase().replace(/\s+/g, '-'); + // Create a sanitized project name for test ID (lowercase, URL-safe slug) + const sanitizedName = + project.name + .toLowerCase() + .trim() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') || `project-${project.id}`;apps/ui/src/components/views/spec-view/types.ts (1)
1-2: Consider renaming to avoid collision with theSpecViewModecomponent.The type
SpecViewModeshares a name with theSpecViewModecomponent exported from./spec-view-mode.tsx. While TypeScript distinguishes between type and value imports, this can cause confusion when importing both:import { SpecViewMode } from './spec-view-mode'; // component import type { SpecViewMode } from '../types'; // type - collisionConsider renaming the type to
SpecViewModeTypeorViewModeto improve clarity.apps/ui/src/components/views/spec-view/components/spec-mode-tabs.tsx (2)
29-44: Consider adding accessible labels for icon-only tabs on mobile.On small screens (
sm:hiddentext), tabs display only icons. Screen reader users may not understand what each tab represents. Consider addingaria-labelattributes to the triggers.♿ Suggested improvement for accessibility
<TabsTrigger value="view" disabled={disabled || !isParseValid} title={!isParseValid ? 'Fix XML errors to enable View mode' : 'View formatted spec'} + aria-label="View" > <Eye className="w-4 h-4" /> <span className="hidden sm:inline">View</span> </TabsTrigger> <TabsTrigger value="edit" disabled={disabled || !isParseValid} title={!isParseValid ? 'Fix XML errors to enable Edit mode' : 'Edit spec with form'} + aria-label="Edit" > <Edit3 className="w-4 h-4" /> <span className="hidden sm:inline">Edit</span> </TabsTrigger> - <TabsTrigger value="source" disabled={disabled} title="Edit raw XML source"> + <TabsTrigger value="source" disabled={disabled} title="Edit raw XML source" aria-label="Source"> <Code className="w-4 h-4" /> <span className="hidden sm:inline">Source</span> </TabsTrigger>
18-20: Type assertion is acceptable but could be more defensive.The cast
value as SpecViewModeis safe given the hardcoded tab values, but for robustness you could validate before casting. This is optional given the controlled context.apps/ui/src/components/views/spec-view/components/edit-mode/features-section.tsx (2)
156-178: Fallback key pattern may cause reconciliation issues.The fallback
fallback-${idx}at line 158 could cause React reconciliation issues if_locationIdsbecomes out of sync withfile_locations. SincefeatureToInternalalways generates_locationIds, this fallback should rarely trigger, but consider logging a warning in development if it does to catch bugs early.
97-98: Consider future drag-and-drop implementation.The
GripVerticalicon suggests drag-and-drop reordering capability. If this is planned for later, consider adding a comment or tracking it as a TODO.apps/ui/src/components/views/spec-view/components/spec-edit-mode.tsx (1)
24-46: JSON.stringify comparison has key ordering limitations.Using
JSON.stringifyfor deep comparison (lines 25, 32, 42) works for most cases but is sensitive to key ordering. If thespecobject's keys could be reordered between renders (e.g., from different serialization sources), this could cause unnecessary resets. In practice this is usually fine for structured data likeSpecOutput, but worth noting.libs/spec-parser/src/validate.ts (1)
130-143: Consider adding closing tag check for consistency.The root element check only verifies the opening tag while other element checks use regex that implicitly require both tags. For a more robust quick check, consider also verifying the closing tag exists:
💡 Optional improvement
// Check for essential elements - const hasRoot = xmlContent.includes('<project_specification>'); + const hasRoot = + xmlContent.includes('<project_specification>') && + xmlContent.includes('</project_specification>');apps/ui/src/components/views/spec-view/hooks/use-spec-parser.ts (1)
41-49: Consider whether pre-check masks detailed errors.The
isValidSpecXmlpre-check returns a generic "Invalid XML structure - missing required elements" error. When this fails, the more detailed errors fromxmlToSpecare bypassed. If users would benefit from specific field-level error messages, consider lettingxmlToSpechandle all validation, or include which elements are missing in the quick-check error.This is a trade-off between performance (fast-fail) and error specificity. The current approach is reasonable for obviously malformed input.
apps/ui/src/components/views/spec-view/components/edit-mode/array-field-editor.tsx (1)
40-48: External sync regenerates all IDs, causing full remount.When
valueschanges externally, all items receive new UUIDs (Line 47), causing React to remount every item. This works correctly but may cause minor UX issues if the parent inadvertently changes thevaluesarray reference with identical content.Consider a shallow comparison to avoid unnecessary ID regeneration:
💡 Optional: Avoid remount when content hasn't changed
useEffect(() => { if (isInternalChange.current) { isInternalChange.current = false; return; } - // External change - rebuild items with new IDs - setItems(values.map((value) => ({ id: generateId(), value }))); + // External change - only rebuild if content actually changed + const currentValues = items.map((item) => item.value); + const valuesMatch = + values.length === currentValues.length && + values.every((v, i) => v === currentValues[i]); + if (!valuesMatch) { + setItems(values.map((value) => ({ id: generateId(), value }))); + } }, [values]);
- Remove unused RefreshCw import from spec-view.tsx - Add explicit parsedSpec check in renderContent for robustness - Hide save button in view mode since it's read-only - Remove GripVertical drag handles since drag-and-drop is not implemented - Rename Map imports to MapIcon to avoid shadowing global Map - Escape tagName in xml-utils.ts RegExp functions for safety - Add aria-label attributes for accessibility on mode tabs
00e9868 to
e9bffb9
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In
`@apps/ui/src/components/views/spec-view/components/edit-mode/roadmap-section.tsx`:
- Around line 92-97: The Textarea is receiving phase.description which is
optional on RoadmapPhase, causing controlled/uncontrolled warnings; update the
value prop in the edit-mode component (where Textarea is used) to pass a safe
default like an empty string (e.g., use phase.description ?? "" or
String(phase.description || "")) so the Textarea remains controlled, and ensure
handleDescriptionChange still updates the phase model (referencing Textarea,
phase.description, and handleDescriptionChange).
- Around line 122-129: The current useEffect syncing external phases recreates
internal items by calling phaseToInternal for every phase, causing new _id
values and unmounting PhaseCard components; instead, update the effect to
preserve existing item IDs by reconciling phases with current items: iterate
phases and for each phase try to find a matching existing item in items (by
original phase id, by content equality, or at least by index) and reuse its _id
when building the new internal item before calling setItems; keep
isInternalChange logic intact and only regenerate ids for truly new phases that
have no match.
In `@libs/spec-parser/src/xml-utils.ts`:
- Around line 37-69: The extractXmlSection and extractXmlElements functions
currently use regexes that only match bare tags (they build regex from
escapeRegExp(tagName) as `<${safeTag}>…</${safeTag}>`) and therefore do not
support tags with attributes; update the JSDoc for both extractXmlSection and
extractXmlElements to explicitly state this limitation (that tags with
attributes like `<tag id="1">` are not supported), referencing the regex usage
so future maintainers know why, or, if attribute tolerance is required later,
adjust the regexes used in those functions to allow optional attributes after
the opening tag name.
🧹 Nitpick comments (3)
apps/ui/src/components/views/spec-view/components/edit-mode/features-section.tsx (1)
156-157: Fallback key using index may cause instability.The
fallback-${idx}key is used when_locationIdsis undefined or shorter thanfile_locations. SincefeatureToInternalalways generates_locationIdsfrom the locations array, this fallback should rarely trigger. However, if synchronization ever fails (e.g., edge case in external data), the index-based fallback can cause React reconciliation issues when locations are added/removed.Consider making this more defensive by ensuring
_locationIdsis always in sync, or logging a warning when the fallback is used to catch potential bugs early.apps/ui/src/components/views/spec-view.tsx (1)
198-216: Thedisabled={isProcessing}prop on line 207 is effectively unused.The parent conditional on line 201 (
!isProcessing &&) ensures theSpecModeTabscomponent only renders whenisProcessingis false, sodisabled={isProcessing}will always evaluate tofalse.Consider removing the redundant prop or, if the intent is to show disabled tabs during processing, render the tabs unconditionally and rely solely on the
disabledprop.Option 1: Remove the redundant prop
<SpecModeTabs mode={mode} onModeChange={handleModeChange} isParseValid={isParseValid} - disabled={isProcessing} />Option 2: Show disabled tabs during processing (if intended)
- {!isProcessing && ( <div className="flex items-center justify-center px-4 py-2 border-b border-border bg-muted/30 relative"> <SpecModeTabs mode={mode} onModeChange={handleModeChange} isParseValid={isParseValid} disabled={isProcessing} /> ... </div> - )}apps/ui/src/components/views/spec-view/components/edit-mode/roadmap-section.tsx (1)
1-16: Imports look good; Map shadowing issue resolved.The
Mapicon is correctly aliased toMapIcon(line 15) addressing the previous review concern. The@automaker/spec-parserimport follows the coding guidelines.Minor nit: the two
lucide-reactimports (lines 1 and 15) could be consolidated into one.
- Fix Textarea controlled/uncontrolled warning with default value - Preserve IDs in useEffect sync to avoid unnecessary remounts - Consolidate lucide-react imports - Add JSDoc note about tag attributes limitation in xml-utils.ts - Remove redundant disabled prop from SpecModeTabs
Summary
Enhances the
/specpage with three distinct viewing modes for a better user experience:New Shared Package
Adds
@automaker/spec-parserlibrary for XML parsing that can be shared between server and client:xmlToSpec()- Parse XML to SpecOutput objectspecToXml()- Convert SpecOutput back to XMLTest plan
Summary by CodeRabbit
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.