Skip to content

Replace TaggedValue tabular variants for color and gradient with non-tabular forms#4038

Draft
Keavon wants to merge 1 commit intomasterfrom
no-tables-in-widgets-for-colors-gradients
Draft

Replace TaggedValue tabular variants for color and gradient with non-tabular forms#4038
Keavon wants to merge 1 commit intomasterfrom
no-tables-in-widgets-for-colors-gradients

Conversation

@Keavon
Copy link
Copy Markdown
Member

@Keavon Keavon commented Apr 23, 2026

We might not merge this, instead we may treat everything (including tables of color) as baked partial graph computations and that would be what the color picker widgets manipulate.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the handling of colors and gradients by replacing Table-wrapped types with direct Color, Option<Color>, and GradientStops variants within TaggedValue. The changes span across node definitions, document migration logic, and UI property widgets to support these more concise types. The review identifies critical issues where the removal of tabular variants from TaggedValue and the property panel matching logic could break monitoring, memoization, and UI display for nodes still returning tabular data. Additionally, suggestions are provided to include missing type implementations for the color_value and empty_image nodes to ensure they function correctly with the new constants.

Comment on lines +209 to +214
Color(Color),
#[serde(deserialize_with = "core_types::misc::migrate_optional_color")] // TODO: Eventually remove this migration document upgrade code
OptionalColor(Option<Color>),
#[serde(deserialize_with = "graphic_types::vector_types::gradient::migrate_gradient_stops")] // TODO: Eventually remove this migration document upgrade code
#[serde(alias = "GradientPositions", alias = "GradientTable")]
GradientStops(GradientStops),
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.

high

Removing the Table<Color> and Table<GradientStops> variants from TaggedValue while they are still used as return types in the graph (e.g., in color_value, rgba_to_color, and sample_gradient) will cause try_from_any and try_from_std_any_ref to fail for these types. This will break monitoring and memoization for any node returning these tabular forms.

Since the tagged_value! macro (defined at line 33) generates these conversion methods by iterating over the variants, you should either:

  1. Add manual conversion logic to the macro definition to map these Table types to the new non-tabular variants.
  2. Keep the tabular variants as internal/deprecated forms to support runtime conversion of existing graph outputs.

Comment on lines +225 to +227
Some(x) if x == TypeId::of::<Color>() => color_widget(default_info, ColorInput::default().allow_none(false)),
Some(x) if x == TypeId::of::<Option<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)),
Some(x) if x == TypeId::of::<GradientStops>() => color_widget(default_info, ColorInput::default().allow_none(false)),
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.

high

The removal of Table<Color> and Table<GradientStops> from the TypeId matching in property_from_type causes a UI regression. Many nodes in the graph still return these tabular types (e.g., color_value, rgba_to_color, sample_gradient). For these nodes, the Properties panel will now fail to display a color picker widget and instead show a fallback message indicating that no widget exists for the type.

These cases should be restored to maintain UI compatibility with nodes that still produce tabular return types. Note that color_widget will still work correctly because TaggedValue::from_type (used for initializing widgets) already maps these tabular types to the new OptionalColor and GradientStops variants.

Suggested change
Some(x) if x == TypeId::of::<Color>() => color_widget(default_info, ColorInput::default().allow_none(false)),
Some(x) if x == TypeId::of::<Option<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)),
Some(x) if x == TypeId::of::<GradientStops>() => color_widget(default_info, ColorInput::default().allow_none(false)),
Some(x) if x == TypeId::of::<Color>() => color_widget(default_info, ColorInput::default().allow_none(false)),
Some(x) if x == TypeId::of::<Option<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)),
Some(x) if x == TypeId::of::<graphene_std::table::Table<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)),
Some(x) if x == TypeId::of::<GradientStops>() => color_widget(default_info, ColorInput::default().allow_none(false)),
Some(x) if x == TypeId::of::<graphene_std::table::Table<GradientStops>>() => color_widget(default_info, ColorInput::default().allow_none(false)),

_: impl Ctx,
_primary: (),
#[default(Color::BLACK)]
#[implementations(Option<Color>, Table<Color>)]
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.

medium

The color_value node should include Color in its #[implementations] list. Since the UI now provides TaggedValue::Color(Color) for non-optional color inputs, the compiler will attempt to resolve this node with C = Color. Without this implementation, the node will fail to compile when used with a constant color value from the Properties panel.

Suggested change
#[implementations(Option<Color>, Table<Color>)]
#[implementations(Color, Option<Color>, Table<Color>)]


#[node_macro::node(category("Debug"))]
pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Table<Color>) -> Table<Raster<CPU>> {
pub fn empty_image<C: Into<Table<Color>>>(_: impl Ctx, transform: DAffine2, #[implementations(Color, Table<Color>)] color: C) -> Table<Raster<CPU>> {
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.

medium

The empty_image node is missing an implementation for Option<Color>. Because Table<Color> now maps to TaggedValue::OptionalColor in from_type, the UI will provide an Option<Color> to this node. Adding this implementation ensures the node remains functional with the new non-tabular color constants.

Suggested change
pub fn empty_image<C: Into<Table<Color>>>(_: impl Ctx, transform: DAffine2, #[implementations(Color, Table<Color>)] color: C) -> Table<Raster<CPU>> {
pub fn empty_image<C: Into<Table<Color>>>(_: impl Ctx, transform: DAffine2, #[implementations(Color, Option<Color>, Table<Color>)] color: C) -> Table<Raster<CPU>> {

@github-actions github-actions Bot temporarily deployed to graphite-dev (Preview) April 23, 2026 03:43 Inactive
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 17 files

Confidence score: 3/5

  • There is a concrete regression risk in editor/src/messages/portfolio/document_migration.rs: the migration currently forces exposed = false, which can silently change existing document behavior for users with previously exposed inputs.
  • The issue is medium severity (6/10) with high confidence (9/10), so this is more than a cosmetic concern and justifies a moderate merge-risk score.
  • Pay close attention to editor/src/messages/portfolio/document_migration.rs - preserve the original exposure flag during migration to avoid silently unexposing inputs.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="editor/src/messages/portfolio/document_migration.rs">

<violation number="1" location="editor/src/messages/portfolio/document_migration.rs:1997">
P2: The color migration forcibly sets `exposed` to `false`, which can silently unexpose previously exposed inputs. Preserve the original exposure flag during conversion.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment on lines +1997 to +2005
&& let Some(NodeInput::Value { tagged_value, .. }) = node.inputs.get(input_index)
&& let TaggedValue::Color(color) = &**tagged_value
{
let color = *color;

document.network_interface.set_input(
&InputConnector::node(*node_id, input_index),
NodeInput::value(TaggedValue::OptionalColor(Some(color)), false),
network_path,
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.

P2: The color migration forcibly sets exposed to false, which can silently unexpose previously exposed inputs. Preserve the original exposure flag during conversion.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/portfolio/document_migration.rs, line 1997:

<comment>The color migration forcibly sets `exposed` to `false`, which can silently unexpose previously exposed inputs. Preserve the original exposure flag during conversion.</comment>

<file context>
@@ -1986,6 +1986,31 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
+	];
+	for &(ref identifier, input_index) in color_to_optional_cases {
+		if reference == DefinitionIdentifier::ProtoNode(identifier.clone())
+			&& let Some(NodeInput::Value { tagged_value, .. }) = node.inputs.get(input_index)
+			&& let TaggedValue::Color(color) = &**tagged_value
+		{
</file context>
Suggested change
&& let Some(NodeInput::Value { tagged_value, .. }) = node.inputs.get(input_index)
&& let TaggedValue::Color(color) = &**tagged_value
{
let color = *color;
document.network_interface.set_input(
&InputConnector::node(*node_id, input_index),
NodeInput::value(TaggedValue::OptionalColor(Some(color)), false),
network_path,
&& let Some(NodeInput::Value { tagged_value, exposed }) = node.inputs.get(input_index)
&& let TaggedValue::Color(color) = &**tagged_value
{
let color = *color;
document.network_interface.set_input(
&InputConnector::node(*node_id, input_index),
NodeInput::value(TaggedValue::OptionalColor(Some(color)), *exposed),
network_path,
);

@Keavon Keavon marked this pull request as draft April 23, 2026 04:09
@Keavon Keavon force-pushed the no-tables-in-widgets-for-colors-gradients branch from 1eb2ae9 to b50b47f Compare April 23, 2026 09:29
@github-actions github-actions Bot temporarily deployed to graphite-dev (Preview) April 23, 2026 09:42 Inactive
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.

1 participant