Skip to content

feat(properties): edit task properties from list, task code cleanup#992

Merged
sedson merged 19 commits intomainfrom
seamus/edit-props-from-soup
Jan 14, 2026
Merged

feat(properties): edit task properties from list, task code cleanup#992
sedson merged 19 commits intomainfrom
seamus/edit-props-from-soup

Conversation

@sedson
Copy link
Copy Markdown
Contributor

@sedson sedson commented Jan 14, 2026

No description provided.

@sedson sedson changed the title seamus/edit props from soup feat(properties): edit task properties from list, task code cleanup Jan 14, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Jan 14, 2026

@sedson sedson marked this pull request as ready for review January 14, 2026 14:30
@sedson sedson requested a review from a team as a code owner January 14, 2026 14:30
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Jan 14, 2026

Code Review

Found 1 high-confidence issue:

SolidJS Hook Rule Violation

File: js/app/packages/core/component/Properties/hooks/usePropertyEntityDisplay.tsx
Lines: 72-104

This code violates SolidJS hook rules by calling hooks conditionally inside functions that are re-evaluated on each render.

Problems:

  1. Lines 72-80 (previewManager): Calls useItemPreview() inside a function that gets re-evaluated whenever entityType() changes. Each re-evaluation creates new effects and subscriptions without cleaning up old ones.

  2. Lines 84-89 (channelNameManager): Calls useChannelName() conditionally inside a re-evaluated function, creating new memos on each call.

  3. Lines 101-104 (nameFn): Calls useDisplayName() inside a match expression, creating new hooks during reactive evaluations.

In SolidJS, hooks must be called at the top level during component/hook initialization, not inside functions that get re-evaluated. This causes memory leaks, multiple subscriptions, and unpredictable behavior.

Comparison with old code:

The old code properly called hooks at the top level:

const [preview] = useItemPreview({
  id: needsPreview() ? entityId() : '',
  type: needsPreview() ? getPreviewType() : undefined,
});

const channelName = useChannelName(
  entityType().toUpperCase() === 'CHANNEL' ? entityId() : '',
  'Unknown Channel'
);

Recommended fix:

Call all hooks unconditionally at the top level with reactive parameters:

// Call hooks at top level with reactive parameters
const [preview] = useItemPreview({
  id: () => isPreviewable(entityType()) ? entityId() : '',
  type: previewType,
});

const channelName = useChannelName(
  () => previewType() === 'channel' ? entityId() : '',
  'Channel'
);

const [userName] = useDisplayName(
  () => entityType() === 'USER' ? tryMacroId(entityId()) : undefined
);

// Then use these in reactive contexts
const name = createMemo(() => {
  return match(entityType())
    .with('USER', () => userName())
    .with('CHANNEL', () => channelName() ?? 'Channel')
    // ...
});

This ensures hooks are only instantiated once during initialization, following SolidJS best practices.


Review Summary:

  • ✅ No CLAUDE.md violations found
  • ❌ 1 high-confidence bug found (SolidJS hook violation)

Copy link
Copy Markdown
Contributor

@dev-rb dev-rb left a comment

Choose a reason for hiding this comment

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

Looks good overall, just a few nits

Copy link
Copy Markdown
Contributor

@synoet synoet left a comment

Choose a reason for hiding this comment

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

Very Nice! One small note for onRefresh.

Comment on lines +61 to +65
const saveMutation = useSaveEntityPropertyMutation({
onSuccess: () => {
props.onRefresh?.();
},
});
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.

We shouldn't need to manually trigger refreshes everywhere. The mutation should optimistically update and invalidate. And since everything is using the solid queries they should just reactively update.

Comment on lines +94 to +95
onPropertyAdded={() => props.onRefresh?.()}
onPropertyDeleted={() => props.onRefresh?.()}
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.

Same note about onRefresh

@sedson sedson requested a review from dev-rb January 14, 2026 15:20
Comment on lines +59 to +100
/**
* Creates a reactive hook manager that properly disposes and recreates hooks
* when dependencies change. This solves the problem of hooks that take plain
* values (not accessors) needing to react to changes.
* @param deps - Accessor that returns dependencies to track
* @param shouldCreate - Predicate to determine if hook should be created
* @param createHook - Factory function that creates the hook and returns its value
* @param defaultValue - Default value when hook is not created
* @returns Accessor to the current hook value
*/
function useReactiveHook<TDeps, TValue>(
deps: Accessor<TDeps>,
shouldCreate: (deps: TDeps) => boolean,
createHook: (deps: TDeps) => TValue,
defaultValue: TValue
): Accessor<TValue> {
const [value, setValue] = createSignal<TValue>(defaultValue);
let dispose: (() => void) | undefined;

createEffect(() => {
const currentDeps = deps();

// Clean up previous hook instance
dispose?.();
dispose = undefined;

if (shouldCreate(currentDeps)) {
createRoot((d) => {
dispose = d;
const hookValue = createHook(currentDeps);
setValue(() => hookValue);
});
} else {
setValue(() => defaultValue);
}
});

onCleanup(() => dispose?.());

return value;
}

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.

This is very complex and seems unnecessary. I think the previous change with the memo was fine but just required small changes. I think Claude thinks this is React 🤦

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.

Yeah, this is cope.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah fair enough. the memo is not enough to smartly dispose the computations inside the usePreview() hook though right? like the "old" usePreview will still just be disposed with the lifecycle of the calling component?

@sedson sedson merged commit d5fb406 into main Jan 14, 2026
22 checks passed
@sedson sedson deleted the seamus/edit-props-from-soup branch January 14, 2026 16:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants