Skip to content

issue/3857/feat/copying-distributions#4538

Open
lsabor wants to merge 1 commit intomainfrom
issue/3857/feat/copying-distributions
Open

issue/3857/feat/copying-distributions#4538
lsabor wants to merge 1 commit intomainfrom
issue/3857/feat/copying-distributions

Conversation

@lsabor
Copy link
Copy Markdown
Contributor

@lsabor lsabor commented Mar 25, 2026

closes #3857

implements copy and paste from arbitrary distributions
support ctrl+c and ctrl+v

Summary by CodeRabbit

  • New Features

    • Added clipboard copy and paste functionality for forecast distributions with validation.
    • Keyboard shortcut support (Ctrl/Cmd+C to copy, Ctrl/Cmd+V to paste).
    • Real-time feedback for successful and failed clipboard operations.
    • Automatic mode switching when pasting distributions.
  • Localization

    • Added translation strings for distribution clipboard operations in six languages (Czech, English, Spanish, Portuguese, Simplified Chinese, Traditional Chinese).

closes #3859
implements copy and paste from arbitrary distributions
support ctrl+c and ctrl+v
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

This PR adds multilingual localization strings for distribution clipboard operations (copy/paste success/failure messages and action labels) across six language files, and implements a new ContinuousClipboardMenu component that enables users to copy and paste continuous forecast distribution data via browser clipboard with validation, keyboard shortcuts, and toast notifications.

Changes

Cohort / File(s) Summary
Localization Strings
front_end/messages/cs.json, front_end/messages/en.json, front_end/messages/es.json, front_end/messages/pt.json, front_end/messages/zh-TW.json, front_end/messages/zh.json
Added six identical i18n keys for distribution clipboard interactions: distributionCopied, distributionCopyFailed, distributionPasted, distributionPasteInvalid, copyDistribution, pasteDistribution. Each file adds +6 lines with no modifications to existing keys.
Clipboard Menu Component
front_end/src/components/forecast_maker/continuous_input/continuous_clipboard_menu.tsx
New component exposing copy and paste icon buttons with clipboard serialization/validation logic. Implements navigator.clipboard API calls with JSON validation (marker check, type verification, field presence), Ctrl/Cmd keyboard shortcuts, and toast-based success/failure feedback. Exports Props interface defining forecastInputMode, sliderComponents, quantileComponents, onPaste callback, optional disabled flag, and containerRef.
Input Container & Wrapper
front_end/src/components/forecast_maker/continuous_input/continuous_input_container.tsx, front_end/src/components/forecast_maker/continuous_input/index.tsx
Added optional clipboardData prop to accept slider/quantile components and onPaste callback. Container creates a containerRef for keyboard listener attachment and conditionally renders ContinuousClipboardMenu when data is provided. Parent index component forwards the prop through the hierarchy.
Forecast Makers
front_end/src/components/forecast_maker/forecast_maker_group/continuous_input_wrapper.tsx, front_end/src/components/forecast_maker/forecast_maker_question/forecast_maker_continuous.tsx
Implemented handleClipboardPaste callbacks that route pasted data into parent state management via handleChange, mark quantile components as dirty, switch input mode via setForecastInputMode, and pass current component state plus handler to ContinuousInput via clipboardData prop.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CM as ContinuousClipboardMenu
    participant CB as Browser Clipboard
    participant Parent as Parent State Manager

    Note over CM: Copy Flow
    User->>CM: Click copy button (or Ctrl+C)
    CM->>CM: Serialize {marker, type, sliders/quantiles}
    CM->>CB: navigator.clipboard.writeText(JSON)
    CB-->>CM: Success/Error
    alt Copy Success
        CM->>User: Toast: distributionCopied
    else Copy Failed
        CM->>User: Toast: distributionCopyFailed
    end

    Note over CM: Paste Flow
    User->>CM: Click paste button (or Ctrl+V)
    CM->>CB: navigator.clipboard.readText()
    CB-->>CM: Clipboard text
    CM->>CM: Parse JSON & validate<br/>(marker check, type check)
    alt Validation Success
        CM->>Parent: onPaste(type, components)
        Parent->>Parent: Update state<br/>Mark quantiles dirty<br/>Switch input mode
        CM->>User: Toast: distributionPasted
    else Validation Failed
        CM->>User: Toast: distributionPasteInvalid
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • elisescu
  • cemreinanc
  • hlbmtc

Poem

🐰 A clipboard dance, so neat and tidy,
Copy, paste, distribute widely!
Six tongues whisper, "Success!" they cry,
With Ctrl+C and V, forecasts fly! ✨📋

🚥 Pre-merge checks | ✅ 2 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements clipboard copy/paste for distributions, but the linked issue #3859 describes fixing a critical React Server Components security vulnerability, which appears unrelated to the actual changes. Verify the correct linked issue. The changes implement distribution clipboard functionality, not a React security vulnerability fix. Link to the appropriate issue if one exists.
Title check ❓ Inconclusive The title references a feature branch naming pattern rather than describing the actual change. It should convey what the PR does (copying/pasting distributions), not just cite the issue number. Consider revising to a more descriptive title like 'Add clipboard copy/paste support for distributions' that clearly communicates the feature being added.
✅ Passed checks (2 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed All changes consistently implement clipboard copy/paste functionality for distributions across UI components and localizations, with no unrelated modifications detected.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue/3857/feat/copying-distributions

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@front_end/messages/zh-TW.json`:
- Around line 2081-2086: Update the translation values for the keys
"distributionCopied", "distributionCopyFailed", "distributionPasted",
"distributionPasteInvalid", "copyDistribution", and "pasteDistribution" to use
「分佈」 instead of 「分配」 so the terminology matches the forecasting context; locate
these string values in front_end/messages/zh-TW.json (the keys listed above) and
replace each occurrence of 「分配」 with 「分佈」 while preserving the rest of the
messages.

In
`@front_end/src/components/forecast_maker/continuous_input/continuous_clipboard_menu.tsx`:
- Around line 24-36: The isClipboardDistribution type guard currently checks
marker and type but must also validate that (data as
ClipboardDistribution).components is an array and that each component has the
required shape: ensure components is Array.isArray(...) and for each element
verify a discriminant (e.g., component.type or similar) and then for slider
components confirm presence and correct types for weight, left, center, right,
and for quantile components confirm presence and correct type for quantile; keep
referencing CLIPBOARD_MARKER, VALID_INPUT_TYPES and the ClipboardDistribution
shape and update isClipboardDistribution to return false for any malformed
component to prevent downstream runtime errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2628ab1d-d8ec-4edf-b54a-c454fc13fba0

📥 Commits

Reviewing files that changed from the base of the PR and between f1c7c45 and 52662cd.

📒 Files selected for processing (11)
  • front_end/messages/cs.json
  • front_end/messages/en.json
  • front_end/messages/es.json
  • front_end/messages/pt.json
  • front_end/messages/zh-TW.json
  • front_end/messages/zh.json
  • front_end/src/components/forecast_maker/continuous_input/continuous_clipboard_menu.tsx
  • front_end/src/components/forecast_maker/continuous_input/continuous_input_container.tsx
  • front_end/src/components/forecast_maker/continuous_input/index.tsx
  • front_end/src/components/forecast_maker/forecast_maker_group/continuous_input_wrapper.tsx
  • front_end/src/components/forecast_maker/forecast_maker_question/forecast_maker_continuous.tsx

Comment on lines +2081 to +2086
"distributionCopied": "分配已複製到剪貼簿",
"distributionCopyFailed": "複製分配失敗",
"distributionPasted": "從剪貼簿貼上了分配",
"distributionPasteInvalid": "剪貼簿中未找到有效的分配",
"copyDistribution": "將分配複製到剪貼簿",
"pasteDistribution": "從剪貼簿貼上分配",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use 「分佈」 instead of 「分配」 for consistency and correctness.

In this locale file, distribution in forecasting context is already translated as 「分佈」. Using 「分配」 here introduces inconsistent terminology and reads as “allocation.”

Suggested wording update
-  "distributionCopied": "分配已複製到剪貼簿",
-  "distributionCopyFailed": "複製分配失敗",
-  "distributionPasted": "從剪貼簿貼上了分配",
-  "distributionPasteInvalid": "剪貼簿中未找到有效的分配",
-  "copyDistribution": "將分配複製到剪貼簿",
-  "pasteDistribution": "從剪貼簿貼上分配",
+  "distributionCopied": "分佈已複製到剪貼簿",
+  "distributionCopyFailed": "複製分佈失敗",
+  "distributionPasted": "已從剪貼簿貼上分佈",
+  "distributionPasteInvalid": "剪貼簿中未找到有效的分佈",
+  "copyDistribution": "將分佈複製到剪貼簿",
+  "pasteDistribution": "從剪貼簿貼上分佈",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"distributionCopied": "分配已複製到剪貼簿",
"distributionCopyFailed": "複製分配失敗",
"distributionPasted": "從剪貼簿貼上了分配",
"distributionPasteInvalid": "剪貼簿中未找到有效的分配",
"copyDistribution": "將分配複製到剪貼簿",
"pasteDistribution": "從剪貼簿貼上分配",
"distributionCopied": "分佈已複製到剪貼簿",
"distributionCopyFailed": "複製分佈失敗",
"distributionPasted": "已從剪貼簿貼上分佈",
"distributionPasteInvalid": "剪貼簿中未找到有效的分佈",
"copyDistribution": "將分佈複製到剪貼簿",
"pasteDistribution": "從剪貼簿貼上分佈",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@front_end/messages/zh-TW.json` around lines 2081 - 2086, Update the
translation values for the keys "distributionCopied", "distributionCopyFailed",
"distributionPasted", "distributionPasteInvalid", "copyDistribution", and
"pasteDistribution" to use 「分佈」 instead of 「分配」 so the terminology matches the
forecasting context; locate these string values in front_end/messages/zh-TW.json
(the keys listed above) and replace each occurrence of 「分配」 with 「分佈」 while
preserving the rest of the messages.

Comment on lines +24 to +36
function isClipboardDistribution(
data: unknown
): data is ClipboardDistribution {
return (
typeof data === "object" &&
data !== null &&
CLIPBOARD_MARKER in data &&
(data as ClipboardDistribution)[CLIPBOARD_MARKER] === true &&
"type" in data &&
"components" in data &&
VALID_INPUT_TYPES.includes((data as ClipboardDistribution).type)
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Strengthen validation to include component structure checks.

The isClipboardDistribution type guard validates the marker and type but does not verify the structure of components. Malformed clipboard data could cause runtime errors downstream when:

  • components is not an array (.map() will fail)
  • Slider components are missing required fields (weight, left, center, right)
  • Quantile components are missing the quantile field
🛡️ Proposed fix to add component structure validation
+function isValidSliderComponent(c: unknown): c is DistributionSliderComponent {
+  return (
+    typeof c === "object" &&
+    c !== null &&
+    typeof (c as DistributionSliderComponent).weight === "number" &&
+    typeof (c as DistributionSliderComponent).left === "number" &&
+    typeof (c as DistributionSliderComponent).center === "number" &&
+    typeof (c as DistributionSliderComponent).right === "number"
+  );
+}
+
+function isValidQuantileComponent(c: unknown): c is DistributionQuantileComponent[number] {
+  return (
+    typeof c === "object" &&
+    c !== null &&
+    "quantile" in c
+  );
+}
+
 function isClipboardDistribution(
   data: unknown
 ): data is ClipboardDistribution {
-  return (
+  if (!(
     typeof data === "object" &&
     data !== null &&
     CLIPBOARD_MARKER in data &&
     (data as ClipboardDistribution)[CLIPBOARD_MARKER] === true &&
     "type" in data &&
     "components" in data &&
     VALID_INPUT_TYPES.includes((data as ClipboardDistribution).type)
-  );
+  )) {
+    return false;
+  }
+
+  const { type, components } = data as ClipboardDistribution;
+  if (!Array.isArray(components) || components.length === 0) {
+    return false;
+  }
+
+  if (type === ContinuousForecastInputType.Slider) {
+    return components.every(isValidSliderComponent);
+  }
+  return components.every(isValidQuantileComponent);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function isClipboardDistribution(
data: unknown
): data is ClipboardDistribution {
return (
typeof data === "object" &&
data !== null &&
CLIPBOARD_MARKER in data &&
(data as ClipboardDistribution)[CLIPBOARD_MARKER] === true &&
"type" in data &&
"components" in data &&
VALID_INPUT_TYPES.includes((data as ClipboardDistribution).type)
);
}
function isValidSliderComponent(c: unknown): c is DistributionSliderComponent {
return (
typeof c === "object" &&
c !== null &&
typeof (c as DistributionSliderComponent).weight === "number" &&
typeof (c as DistributionSliderComponent).left === "number" &&
typeof (c as DistributionSliderComponent).center === "number" &&
typeof (c as DistributionSliderComponent).right === "number"
);
}
function isValidQuantileComponent(c: unknown): c is DistributionQuantileComponent[number] {
return (
typeof c === "object" &&
c !== null &&
"quantile" in c
);
}
function isClipboardDistribution(
data: unknown
): data is ClipboardDistribution {
if (!(
typeof data === "object" &&
data !== null &&
CLIPBOARD_MARKER in data &&
(data as ClipboardDistribution)[CLIPBOARD_MARKER] === true &&
"type" in data &&
"components" in data &&
VALID_INPUT_TYPES.includes((data as ClipboardDistribution).type)
)) {
return false;
}
const { type, components } = data as ClipboardDistribution;
if (!Array.isArray(components) || components.length === 0) {
return false;
}
if (type === ContinuousForecastInputType.Slider) {
return components.every(isValidSliderComponent);
}
return components.every(isValidQuantileComponent);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@front_end/src/components/forecast_maker/continuous_input/continuous_clipboard_menu.tsx`
around lines 24 - 36, The isClipboardDistribution type guard currently checks
marker and type but must also validate that (data as
ClipboardDistribution).components is an array and that each component has the
required shape: ensure components is Array.isArray(...) and for each element
verify a discriminant (e.g., component.type or similar) and then for slider
components confirm presence and correct types for weight, left, center, right,
and for quantile components confirm presence and correct type for quantile; keep
referencing CLIPBOARD_MARKER, VALID_INPUT_TYPES and the ClipboardDistribution
shape and update isClipboardDistribution to return false for any malformed
component to prevent downstream runtime errors.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 25, 2026

🚀 Preview Environment

Your preview environment is ready!

Resource Details
🌐 Preview URL https://metaculus-pr-4538-issue-3857-feat-copying-distri-preview.mtcl.cc
📦 Docker Image ghcr.io/metaculus/metaculus:issue-3857-feat-copying-distributions-52662cd
🗄️ PostgreSQL NeonDB branch preview/pr-4538-issue-3857-feat-copying-distri
Redis Fly Redis mtc-redis-pr-4538-issue-3857-feat-copying-distri

Details

  • Commit: 52662cd6935f18ce3982dedd5cd9bfc0f62917e1
  • Branch: issue/3857/feat/copying-distributions
  • Fly App: metaculus-pr-4538-issue-3857-feat-copying-distri

ℹ️ Preview Environment Info

Isolation:

  • PostgreSQL and Redis are fully isolated from production
  • Each PR gets its own database branch and Redis instance
  • Changes pushed to this PR will trigger a new deployment

Limitations:

  • Background workers and cron jobs are not deployed in preview environments
  • If you need to test background jobs, use Heroku staging environments

Cleanup:

  • This preview will be automatically destroyed when the PR is closed

@lsabor lsabor changed the title issue/3859/feat/copying-distributions issue/3857/feat/copying-distributions Mar 25, 2026
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 support for copying distributions across (non-group) continuous questions

1 participant