-
Notifications
You must be signed in to change notification settings - Fork 2.7k
[WIKI-538] chore: common description component #7785
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: preview
Are you sure you want to change the base?
Conversation
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. WalkthroughIntroduces a generalized DescriptionInput component and DescriptionInputLoader, replaces IssueDescriptionInput usages across multiple issue views, refactors props to an entity-/asset-based API (entityId, fileAssetType, onSubmit, etc.), updates asset upload/search wiring, and adds an index re-export. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant DI as DescriptionInput
participant Caller as Issue view (onSubmit wrapper)
participant Ops as issueOperations
participant API as Backend
User->>DI: Edit description, click submit
DI->>Caller: onSubmit(value) ## new external callback
Caller->>Ops: update(workspaceSlug, project_id, id, {description_html: value})
Ops->>API: PUT /issues/:id {description_html}
API-->>Ops: 200 OK
Ops-->>Caller: Promise resolved
Caller-->>DI: setIsSubmitting(false) / UI update
Note over DI: When description not ready -> show DescriptionInputLoader
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Pre-merge checks (3 passed)✅ Passed checks (3 passed)
Poem
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal). Please share your feedback with us on this Discord post. ✨ Finishing touches
🧪 Generate unit tests
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 |
Pull Request Linked with Plane Work Items
Comment Automatically Generated by Plane |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR refactors the issue description input component by replacing multiple feature-specific description components with a generic DescriptionInput
component. The refactoring standardizes the API for description inputs across the application while maintaining the same functionality.
- Replaces
IssueDescriptionInput
with the new genericDescriptionInput
component - Introduces a reusable description input loader component
- Updates import statements to include new file asset types and utilities
Reviewed Changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
File | Description |
---|---|
apps/web/core/components/issues/peek-overview/issue-detail.tsx |
Updates to use the new DescriptionInput component with standardized props |
apps/web/core/components/issues/issue-detail/main-content.tsx |
Replaces issue-specific description input with generic component |
apps/web/core/components/inbox/content/issue-root.tsx |
Migrates inbox issue description to use the new component and loader |
apps/web/core/components/editor/rich-text/description-input/root.tsx |
Implements the new generic description input component with comprehensive props |
apps/web/core/components/editor/rich-text/description-input/loader.tsx |
Creates a dedicated loader component for description input |
apps/web/core/components/editor/rich-text/description-input/index.ts |
Adds barrel export for the description input components |
Comments suppressed due to low confidence (1)
apps/web/core/components/editor/rich-text/description-input/root.tsx:1
- The
swrDescription
parameter is nullable butprojectId
is not validated for null/undefined before being passed to the API call. Consider adding validation or making the parameter handling consistent.
"use client";
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/web/core/components/issues/peek-overview/issue-detail.tsx (2)
1-1
: Fix client directive.Must be
"use client"
(space), otherwise the file won’t be treated as a client component.-"use-client"; +"use client";
85-90
: Null/undefined check is incorrect (always truthy).Use a nullish check and handle empty string explicitly.
- const issueDescription = - issue.description_html !== undefined || issue.description_html !== null - ? issue.description_html != "" - ? issue.description_html - : "<p></p>" - : undefined; + const issueDescription = + issue.description_html == null + ? undefined + : issue.description_html.trim() === "" + ? "<p></p>" + : issue.description_html;
🧹 Nitpick comments (8)
apps/web/core/components/editor/rich-text/description-input/index.ts (1)
1-1
: Unify public surface: also re-export the loader from the index.This keeps consumers from importing a sibling path for the loader.
export * from "./root"; +export * from "./loader";
apps/web/core/components/editor/rich-text/description-input/loader.tsx (1)
15-17
: Tailwind padding classes are redundant.
p-3 py-2 pt-3
resolves to top: 0.75rem, bottom: 0.5rem, x: 0.75rem. Use explicit shorthands to avoid overrides.- "min-h-[120px] max-h-64 space-y-2 overflow-hidden rounded-md border border-custom-border-200 p-3 py-2 pt-3", + "min-h-[120px] max-h-64 space-y-2 overflow-hidden rounded-md border border-custom-border-200 px-3 pt-3 pb-2",apps/web/core/components/inbox/content/issue-root.tsx (1)
192-196
: Match loader layout with editor container to avoid layout shift.Use the same container classes as the editor for a seamless skeleton.
- {loader === "issue-loading" ? ( - <DescriptionInputLoader /> + {loader === "issue-loading" ? ( + <DescriptionInputLoader className="-ml-3 border-none" />apps/web/core/components/editor/rich-text/description-input/root.tsx (5)
189-207
: Bind Controller value to the editor when SWR value is absent to avoid saving stale form data.Controller currently ignores
value
, so form state can drift from displayed content if no SWR value is provided. Use form value as a fallback.-render={({ field: { onChange } }) => ( +render={({ field: { onChange, value } }) => ( <RichTextEditor @@ - value={swrDescription ?? null} + value={swrDescription ?? value ?? null} @@ onChange={(_description, description_html) => { setIsSubmitting("submitting"); onChange(description_html); hasUnsavedChanges.current = true; debouncedFormSave(); }}
198-199
: Avoid passing empty string as workspaceId.Prefer
undefined
when unknown to prevent accidental "truthy" empty ID handling downstream.-workspaceId={workspaceDetails?.id ?? ""} +workspaceId={workspaceDetails?.id}
209-213
: Don’t sendproject_id: undefined
in search payload.Drop the key when
projectId
is falsy to keep API contracts clean.- await workspaceService.searchEntity(workspaceSlug?.toString() ?? "", { - ...payload, - project_id: projectId, - }) + await workspaceService.searchEntity(workspaceSlug ?? "", { + ...payload, + ...(projectId ? { project_id: projectId } : {}), + })
229-231
: Use console.error for failures and a clearer message.- console.log("Error in uploading asset:", error); + console.error("Error uploading editor asset:", error);
73-74
: TNameDescriptionLoader supports "submitting" and "submitted" — types are correct.Confirmed: packages/types/src/common.ts exports
TNameDescriptionLoader = "submitting" | "submitted" | "saved"
. Optional: replace the string-union with an exported enum/const to avoid magic strings across the codebase.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/web/core/components/editor/rich-text/description-input/index.ts
(1 hunks)apps/web/core/components/editor/rich-text/description-input/loader.tsx
(1 hunks)apps/web/core/components/editor/rich-text/description-input/root.tsx
(4 hunks)apps/web/core/components/inbox/content/issue-root.tsx
(3 hunks)apps/web/core/components/issues/issue-detail/main-content.tsx
(2 hunks)apps/web/core/components/issues/peek-overview/issue-detail.tsx
(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
apps/web/core/components/editor/rich-text/description-input/loader.tsx (1)
packages/ui/src/loader.tsx (1)
Loader
(30-30)
apps/web/core/components/issues/peek-overview/issue-detail.tsx (1)
apps/web/core/components/editor/rich-text/description-input/root.tsx (1)
DescriptionInput
(88-241)
apps/web/core/components/issues/issue-detail/main-content.tsx (1)
apps/web/core/components/editor/rich-text/description-input/root.tsx (1)
DescriptionInput
(88-241)
apps/web/core/components/editor/rich-text/description-input/root.tsx (4)
packages/editor/src/core/types/extensions.ts (1)
TExtensions
(1-1)packages/editor/src/core/types/editor.ts (1)
EditorRefApi
(99-137)packages/utils/src/work-item/base.ts (1)
getDescriptionPlaceholderI18n
(223-227)apps/web/core/components/editor/rich-text/description-input/loader.tsx (1)
DescriptionInputLoader
(9-38)
apps/web/core/components/inbox/content/issue-root.tsx (2)
apps/web/core/components/editor/rich-text/description-input/loader.tsx (1)
DescriptionInputLoader
(9-38)apps/web/core/components/editor/rich-text/description-input/root.tsx (1)
DescriptionInput
(88-241)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Analyze (javascript)
- GitHub Check: Build and lint web apps
🔇 Additional comments (4)
apps/web/core/components/issues/issue-detail/main-content.tsx (1)
137-142
: Await/return the update promise in onSubmit.Return (or await) the promise from issueOperations.update so the debounced autosave only marks "submitted" after persistence.
File: apps/web/core/components/issues/issue-detail/main-content.tsx Lines: 137-142
- onSubmit={async (value) => { + onSubmit={async (value) => { if (!issue.id || !issue.project_id) return; - issueOperations.update(workspaceSlug, issue.project_id, issue.id, { + return issueOperations.update(workspaceSlug, issue.project_id, issue.id, { description_html: value, }); - }} + }}Run to find other similar callsites (avoid process-substitution errors):
#!/bin/bash rg -l --hidden --no-ignore-vcs 'onSubmit=\{\s*async' -g '!node_modules/**' > /tmp/on_submit_files.txt || true rg -l --hidden --no-ignore-vcs 'issueOperations\.update\(' -g '!node_modules/**' > /tmp/update_files.txt || true sort /tmp/on_submit_files.txt -o /tmp/on_submit_files.txt sort /tmp/update_files.txt -o /tmp/update_files.txt comm -12 /tmp/on_submit_files.txt /tmp/update_files.txt || trueapps/web/core/components/inbox/content/issue-root.tsx (1)
153-153
: Good change: escalate logging on failure.Switching to
console.error
improves visibility in monitoring/log capture.apps/web/core/components/editor/rich-text/description-input/root.tsx (2)
84-89
: Nice generalization and clear docstring.The componentization and prop hygiene look good.
45-46
: No action required — RichTextEditor forwards EditorRefApi.
RichTextEditorWithRef (packages/editor) and apps/web's RichTextEditor are declared with forwardRef, so passing editorRef?: React.RefObject to ref is type-compatible.
type TFormData = { | ||
id: string; | ||
description_html: string; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Make description_html
strictly string (prevent undefined) and align initial/reset values.
TFormData.description_html
is typed as string
, but reset()
and local state can set it to undefined
, forcing extra ?? "<p></p>"
later and risking type drift. Normalize at source and drop the redundant fallback in submit.
Apply:
@@
const [localDescription, setLocalDescription] = useState({
- id: entityId,
- description_html: initialValue,
+ id: entityId,
+ description_html:
+ initialValue?.trim() === "" ? "<p></p>" : (initialValue ?? "<p></p>"),
});
@@
const { handleSubmit, reset, control } = useForm<TFormData>({
defaultValues: {
- id: entityId,
- description_html: initialValue || "",
+ id: entityId,
+ description_html:
+ initialValue?.trim() === "" ? "<p></p>" : (initialValue ?? "<p></p>"),
},
});
@@
const handleDescriptionFormSubmit = useCallback(
async (formData: TFormData) => {
- await onSubmit(formData.description_html ?? "<p></p>");
+ await onSubmit(formData.description_html);
},
[onSubmit]
);
@@
reset({
- id: entityId,
- description_html: initialValue?.trim() === "" ? "<p></p>" : initialValue,
+ id: entityId,
+ description_html:
+ initialValue?.trim() === "" ? "<p></p>" : (initialValue ?? "<p></p>"),
});
setLocalDescription({
- id: entityId,
- description_html: initialValue?.trim() === "" ? "<p></p>" : initialValue,
+ id: entityId,
+ description_html:
+ initialValue?.trim() === "" ? "<p></p>" : (initialValue ?? "<p></p>"),
});
Also applies to: 105-108, 119-124, 137-144, 128-131
🤖 Prompt for AI Agents
In apps/web/core/components/editor/rich-text/description-input/root.tsx around
lines 24-27 (and also apply same change to blocks at 105-108, 119-124, 128-131,
137-144): TFormData currently allows description_html to become undefined via
initial state/reset which forces later fallbacks; change the initialization and
reset logic so description_html is always a concrete string (e.g., default to
"<p></p>") and update any local state types to string (not string | undefined),
then remove the redundant "?? '<p></p>'" fallback in submit paths so the value
flow is consistently a non-undefined string. Ensure all assignments, form
resets, and setState calls use the string default to keep types aligned.
// submit handler | ||
const handleDescriptionFormSubmit = useCallback( | ||
async (formData: Partial<TIssue>) => { | ||
await issueOperations.update(workspaceSlug, projectId, issueId, { | ||
description_html: formData.description_html ?? "<p></p>", | ||
}); | ||
async (formData: TFormData) => { | ||
await onSubmit(formData.description_html ?? "<p></p>"); | ||
}, | ||
[workspaceSlug, projectId, issueId, issueOperations] | ||
[onSubmit] | ||
); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Debounced save can capture stale onSubmit; also treat failures without clearing dirty flag.
Current deps omit onSubmit
, so the debounced closure can call an outdated handler. Use a ref to keep the latest callback, and only clear hasUnsavedChanges
on success.
@@
-// submit handler
-const handleDescriptionFormSubmit = useCallback(
- async (formData: TFormData) => {
- await onSubmit(formData.description_html);
- },
- [onSubmit]
-);
+// keep latest submit handler without re-creating debounced fn
+const onSubmitRef = useRef(onSubmit);
+useEffect(() => {
+ onSubmitRef.current = onSubmit;
+}, [onSubmit]);
+
+// submit handler
+const handleDescriptionFormSubmit = useCallback(async (formData: TFormData) => {
+ await onSubmitRef.current(formData.description_html);
+}, []);
@@
-const debouncedFormSave = useCallback(
- debounce(async () => {
- handleSubmit(handleDescriptionFormSubmit)().finally(() => {
- setIsSubmitting("submitted");
- hasUnsavedChanges.current = false;
- });
- }, 1500),
- [entityId, handleSubmit]
-);
+const debouncedFormSave = useCallback(
+ debounce(async () => {
+ handleSubmit(handleDescriptionFormSubmit)()
+ .then(() => {
+ setIsSubmitting("submitted");
+ hasUnsavedChanges.current = false;
+ })
+ .catch((error) => {
+ console.error("Auto-save failed:", error);
+ // consider exposing a 'failed' state if TNameDescriptionLoader supports it
+ });
+ }, 1500),
+ [entityId, handleSubmit]
+);
Also applies to: 152-160
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
apps/web/core/components/issues/peek-overview/issue-detail.tsx (1)
6-11
: Nit: keep import grouping consistent (duplicate of prior feedback).Maintain consistent grouping of plane utils/components imports; this matches project conventions and reduces churn.
🧹 Nitpick comments (1)
apps/web/core/components/issues/peek-overview/issue-detail.tsx (1)
134-139
: Optional: explicitly return the update promise for clarity/tests.Returning the awaited promise makes intent explicit and can help in unit tests that assert on the resolved value.
- await issueOperations.update(workspaceSlug, issue.project_id, issue.id, { + return await issueOperations.update(workspaceSlug, issue.project_id, issue.id, { description_html: value, });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/web/core/components/editor/rich-text/description-input/loader.tsx
(1 hunks)apps/web/core/components/inbox/content/issue-root.tsx
(3 hunks)apps/web/core/components/issues/issue-detail/main-content.tsx
(2 hunks)apps/web/core/components/issues/peek-overview/issue-detail.tsx
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/web/core/components/editor/rich-text/description-input/loader.tsx
- apps/web/core/components/issues/issue-detail/main-content.tsx
- apps/web/core/components/inbox/content/issue-root.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/core/components/issues/peek-overview/issue-detail.tsx (1)
apps/web/core/components/editor/rich-text/description-input/root.tsx (1)
DescriptionInput
(88-241)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build and lint web apps
- GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
apps/web/core/components/issues/peek-overview/issue-detail.tsx (1)
127-143
: LGTM: DescriptionInput integration and autosave flow are correct.Passing editorRef, entityId, fileAssetType, workspaceSlug/projectId, and wiring onSubmit now awaits the update—addressing the earlier “return/await” concern so autosave completion mirrors the actual update.
Description
This PR introduces a new, generic description input component. All the already existing feature specific components are now replaced with this.
Type of Change
Summary by CodeRabbit