-
Notifications
You must be signed in to change notification settings - Fork 1
fix(adhoc-sweep-fixes): CU-86akhf8u5 40 review findings across 33 files #396
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: main
Are you sure you want to change the base?
Changes from all commits
4601f84
2dcebf0
033d842
68f9514
716076b
2d677e3
f5ebb03
366b1bf
d590272
1a6a1a8
3ac4062
bc93aff
97d694d
09743a0
945c9d7
60d85ea
bf23dbd
d5c1642
2aa9952
f34dc96
46d69c6
e5acc31
b028688
96cc4fc
c0de85b
5502387
c1485ea
d8ec753
905a029
88ff1b9
ec67961
adceb04
741e1ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,6 +54,7 @@ jobs: | |
| uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ github.event.pull_request.head.sha }} | ||
| persist-credentials: false | ||
|
|
||
| - name: Set up Node | ||
| uses: actions/setup-node@v4 | ||
|
Comment on lines
54
to
60
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🦩 🟠 test.yml checkout steps omit persist-credentials: false Same mechanism as findings 1 and 2: all three actions/checkout steps in this file (scan, lint, build_image) now consistently set 🤖 Prompt for AI agentsfix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer |
||
|
|
@@ -85,6 +86,7 @@ jobs: | |
| uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ github.event.pull_request.head.sha }} | ||
| persist-credentials: false | ||
|
|
||
| - name: Log in to GitHub Container Registry | ||
| uses: docker/login-action@v3 | ||
|
Comment on lines
86
to
92
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🦩 🟠 build_image job checkout step lacks persist-credentials: false while building/pushing images from untrusted PR content Added 🤖 Prompt for AI agentsfix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer |
||
|
|
@@ -182,3 +184,4 @@ jobs: | |
| - if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} | ||
| run: exit 1 | ||
| - run: echo "All checks passed" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -80,15 +80,17 @@ export function DeviceDetailsView({ deviceId }: DeviceDetailsViewProps) { | |
| }, []); | ||
|
|
||
| // Handle action params from URL (e.g., from table dropdown navigation). Opening | ||
| // the modal is derived state and happens during render — an effect draws the | ||
| // page once without it, so arriving from the table shows a flash of the plain | ||
| // detail view. Clearing the param stays in the effect: it is a navigation. | ||
| // the modal is driven from an effect (rather than during render) so the two | ||
| // setState calls only ever run once per request, are safe under Strict Mode's | ||
| // double-invocation, and never fire after the component has started unmounting. | ||
| const runScriptRequested = searchParams.get('action') === 'runScript' && !isLoading; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🦩 🟠 setState called unconditionally during render body in DeviceDetailsView In 🤖 Prompt for AI agentsfix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer |
||
| const [handledRunScript, setHandledRunScript] = useState(false); | ||
| if (runScriptRequested && !handledRunScript) { | ||
| setHandledRunScript(true); | ||
| setIsScriptsModalOpen(true); | ||
| } | ||
| useEffect(() => { | ||
| if (runScriptRequested && !handledRunScript) { | ||
| setHandledRunScript(true); | ||
| setIsScriptsModalOpen(true); | ||
| } | ||
| }, [runScriptRequested, handledRunScript]); | ||
|
|
||
| useEffect(() => { | ||
| if (!runScriptRequested) return; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,6 +24,11 @@ export function OverviewTab({ device }: OverviewTabProps) { | |
| // Use machineId as the primary device identifier for filtering logs. | ||
| const deviceId = device?.machineId || device?.id; | ||
|
|
||
| // Track the raw search-params string (not just the `refresh` value) so that | ||
| // re-running the same action twice — which can produce an identical | ||
| // `refresh` value — still re-triggers this effect and refreshes the logs. | ||
| const searchParamsString = searchParams?.toString(); | ||
|
|
||
| // Trigger a logs refresh when the `refresh` param changes (e.g. after running a script). | ||
| useEffect(() => { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🦩 🟠 useEffect refresh timer keyed only on refreshParam without dependency on ref stability can silently no-op In 🤖 Prompt for AI agentsfix confidence: 🔴 55 low — review closely — react 👍/👎 to teach the reviewer |
||
| if (refreshParam && logsTableRef.current) { | ||
|
|
@@ -33,7 +38,7 @@ export function OverviewTab({ device }: OverviewTabProps) { | |
| return () => clearTimeout(timer); | ||
| } | ||
| return undefined; | ||
| }, [refreshParam]); | ||
| }, [refreshParam, searchParamsString]); | ||
|
|
||
| return ( | ||
| <div className="flex flex-col gap-[var(--spacing-system-l)]"> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,7 +38,12 @@ export function useDeviceActions(options?: UseDeviceActionsOptions) { | |
| }); | ||
|
|
||
| if (!response.ok) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🦩 🟠 deleteDevice swallows non-Error rejection reasons into a generic message In 🤖 Prompt for AI agentsfix confidence: 🟡 75 medium — react 👍/👎 to teach the reviewer |
||
| throw new Error(response.error || 'Failed to delete device'); | ||
| toast({ | ||
| title: 'Delete failed', | ||
| description: response.error || 'Failed to delete device', | ||
| variant: 'destructive', | ||
| }); | ||
| return false; | ||
| } | ||
|
|
||
| toast({ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -57,7 +57,10 @@ export function buildInstallCommand(options: InstallCommandOptions): string { | |
| return `Set-Location ~; Remove-Item -Path 'openframe-client.zip','openframe-client.exe' -Force -ErrorAction SilentlyContinue; Invoke-WebRequest -Uri '${windowsBinaryUrl}' -OutFile 'openframe-client.zip'; Expand-Archive -Path 'openframe-client.zip' -DestinationPath '.' -Force; & '.\\openframe-client.exe' ${argString}`; | ||
| } | ||
|
|
||
| // macOS / darwin | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🦩 🟠 buildInstallCommand darwin branch mislabels non-macOS Unix platforms as macOS In 🤖 Prompt for AI agentsfix confidence: 🔴 55 low — review closely — react 👍/👎 to teach the reviewer |
||
| // platform !== 'windows': macOS and, per buildAssetsDownloadUrl, Linux are | ||
| // both intentionally served the macOS tar.gz bundle since only two asset | ||
| // bundles are published. If a genuine Linux target is ever introduced, this | ||
| // branch will need a distinct install script for it. | ||
| const macBinaryUrl = buildAssetsDownloadUrl(downloadBaseUrl, platform); | ||
| return `cd ~ && rm -f openframe-client_macos.tar.gz openframe-client 2>/dev/null; curl -fL -o openframe-client_macos.tar.gz '${macBinaryUrl}' && tar -xzf openframe-client_macos.tar.gz && sudo chmod +x ./openframe-client && sudo ./openframe-client ${baseArgs}${extras}`; | ||
| } | ||
|
|
@@ -146,7 +149,8 @@ export function buildUninstallCommand(options: UninstallCommandOptions): string | |
| return `Set-Location ~; Remove-Item -Path 'openframe-client.zip','openframe-client.exe' -Force -ErrorAction SilentlyContinue; Invoke-WebRequest -Uri '${windowsBinaryUrl}' -OutFile 'openframe-client.zip'; Expand-Archive -Path 'openframe-client.zip' -DestinationPath '.' -Force; Start-Process -FilePath '.\\openframe-client.exe' -ArgumentList 'uninstall' -Verb RunAs -Wait`; | ||
| } | ||
|
|
||
| // macOS / darwin | ||
| // platform !== 'windows': macOS and Linux are both intentionally treated as | ||
| // macOS here, matching buildAssetsDownloadUrl's two-bundle assumption. | ||
| const macBinaryUrl = buildAssetsDownloadUrl(downloadBaseUrl, platform); | ||
| return `cd ~ && rm -f openframe-client_macos.tar.gz openframe-client 2>/dev/null; curl -fL -o openframe-client_macos.tar.gz '${macBinaryUrl}' && tar -xzf openframe-client_macos.tar.gz && sudo chmod +x ./openframe-client && sudo ./openframe-client uninstall`; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,7 +33,9 @@ export function ArchiveArticleModal({ isOpen, onClose, article, sourceConnection | |
| toast({ title: 'Article archived', description: article.name, variant: 'success' }); | ||
| onClose(); | ||
| } catch { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🦩 🟠 Identical catch-block comment duplicated verbatim across four modal components Reworded the duplicated catch-block comment in 🤖 Prompt for AI agentsfix confidence: 🔴 30 low — review closely — react 👍/👎 to teach the reviewer |
||
| // The mutation hook already toasts and rejects on failure (see use-archive-article.ts and its siblings). Catching here keeps the rejection from going unhandled and leaves the modal open on the data the user still has, instead of closing it as if the action had succeeded. | ||
| // Swallow: the mutation hook (use-archive-article.ts) already toasts and rejects on | ||
| // failure. Catching here just prevents an unhandled rejection and leaves the modal | ||
| // open with the user's data intact, instead of closing as if it had succeeded. | ||
| } | ||
| }; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -117,7 +117,9 @@ function ArticleDetailsContent({ articleId }: { articleId: string }) { | |
| await publishArticle(article.id); | ||
| toast({ title: 'Published', description: article.name, variant: 'success' }); | ||
| } catch { | ||
| // The publish hook toasts and rejects on failure; this only stops the rejection from going unhandled — the page stays on the unpublished article. | ||
| // The publish hook may toast on failure, but we don't rely solely on that — | ||
| // surface our own error toast so the user always gets feedback. | ||
| toast({ title: 'Failed to publish', description: article.name, variant: 'destructive' }); | ||
| } | ||
| }, [publishArticle, article.id, article.name, toast]); | ||
|
|
||
|
Comment on lines
117
to
125
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🦩 🟠 handleUnpublish silently swallows and mismatches the toast copy from handlePublish In 🤖 Prompt for AI agentsfix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer |
||
|
|
@@ -126,7 +128,8 @@ function ArticleDetailsContent({ articleId }: { articleId: string }) { | |
| await unpublishArticle(article.id); | ||
| toast({ title: 'Moved to draft', description: article.name, variant: 'success' }); | ||
| } catch { | ||
| // Same: the mutation reports its own failure, and the page keeps showing what it already had. | ||
| // Same: don't rely solely on the mutation's own failure toast — show ours too. | ||
| toast({ title: 'Failed to move to draft', description: article.name, variant: 'destructive' }); | ||
| } | ||
| }, [unpublishArticle, article.id, article.name, toast]); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,7 @@ import { Input, Label, LoadError, NotFoundError, PageLayout, Textarea } from '@f | |
| import { useToast } from '@flamingo-stack/openframe-frontend-core/hooks'; | ||
| import { zodResolver } from '@hookform/resolvers/zod'; | ||
| import { useRouter } from 'next/navigation'; | ||
| import { useCallback, useMemo, useState } from 'react'; | ||
| import { useCallback, useEffect, useMemo, useState } from 'react'; | ||
| import { Controller, useForm } from 'react-hook-form'; | ||
| import { z } from 'zod'; | ||
| import { DeviceSelector } from '@/app/components/shared/device-selector'; | ||
|
|
@@ -59,14 +59,21 @@ export function EditPolicyPage({ policyId }: EditPolicyPageProps) { | |
| const [selectedFleetHostIds, setSelectedFleetHostIds] = useState<Set<number>>(new Set()); | ||
| const [hostsInitialized, setHostsInitialized] = useState(false); | ||
|
|
||
| // Initialize selected hosts from current assignment (edit mode) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🦩 🟠 Unconditional setState during render can trigger infinite render loops if hosts array reference changes each fetch In 🤖 Prompt for AI agentsfix confidence: 🟡 70 medium — react 👍/👎 to teach the reviewer |
||
| if (!hostsInitialized && !isLoadingHosts && isExistingPolicy && currentHosts.length > 0) { | ||
| setSelectedFleetHostIds(new Set(currentHosts.map(h => h.id))); | ||
| setHostsInitialized(true); | ||
| } | ||
| if (!hostsInitialized && !isLoadingHosts && (!isExistingPolicy || currentHosts.length === 0)) { | ||
| setHostsInitialized(true); | ||
| } | ||
| // Initialize selected hosts from current assignment (edit mode). | ||
| // Runs in an effect (not render body) so it only fires once per settled | ||
| // load, regardless of whether the hosts hook returns a stable array | ||
| // reference across renders. | ||
| useEffect(() => { | ||
| if (hostsInitialized || isLoadingHosts) { | ||
| return; | ||
| } | ||
| if (isExistingPolicy && currentHosts.length > 0) { | ||
| setSelectedFleetHostIds(new Set(currentHosts.map(h => h.id))); | ||
| setHostsInitialized(true); | ||
| } else if (!isExistingPolicy || currentHosts.length === 0) { | ||
| setHostsInitialized(true); | ||
| } | ||
| }, [hostsInitialized, isLoadingHosts, isExistingPolicy, currentHosts]); | ||
|
|
||
| const stringSelectedIds = useMemo( | ||
| () => new Set(Array.from(selectedFleetHostIds).map(String)), | ||
|
|
@@ -137,7 +144,7 @@ export function EditPolicyPage({ policyId }: EditPolicyPageProps) { | |
| name: data.name, | ||
| description: data.description, | ||
| query: data.query, | ||
| platform: undefined, | ||
| ...(isExistingPolicy && policyDetails ? { platform: policyDetails.platform } : {}), | ||
| }; | ||
|
|
||
| const hostIds = Array.from(selectedFleetHostIds); | ||
|
Comment on lines
144
to
150
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🦩 🟠 onSubmit payload always sends platform: undefined for policy create/update, likely dropping a required field silently In 🤖 Prompt for AI agentsfix confidence: 🔴 45 low — review closely — react 👍/👎 to teach the reviewer |
||
|
|
@@ -168,7 +175,16 @@ export function EditPolicyPage({ policyId }: EditPolicyPageProps) { | |
| }); | ||
| } | ||
| }, | ||
| [isExistingPolicy, numericId, createPolicy, updatePolicy, router, selectedFleetHostIds, replacePolicyHostsMutation], | ||
| [ | ||
| isExistingPolicy, | ||
| numericId, | ||
| policyDetails, | ||
| createPolicy, | ||
| updatePolicy, | ||
| router, | ||
| selectedFleetHostIds, | ||
| replacePolicyHostsMutation, | ||
| ], | ||
| ); | ||
|
|
||
| const onFormError = useCallback( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,10 +49,17 @@ function secondsToUnitValue(totalSeconds: number): { value: number; unit: TimeUn | |
| return { value: totalSeconds / multiplier, unit: unitKey }; | ||
| } | ||
| } | ||
| return { value: Math.ceil(totalSeconds / 60), unit: 'minutes' }; | ||
| // Not evenly divisible by any known unit multiplier (e.g. an interval | ||
| // created outside this form, such as via the API). Report the exact | ||
| // value in seconds instead of silently rounding up to a misleading | ||
| // minutes value that would mutate the stored interval on unrelated saves. | ||
| return { value: totalSeconds, unit: 'seconds' as TimeUnit }; | ||
| } | ||
|
|
||
| function unitValueToSeconds(value: number, unit: TimeUnit): number { | ||
| if (unit === ('seconds' as TimeUnit)) { | ||
| return Math.max(0, Math.floor(value)); | ||
| } | ||
| const found = TIME_UNITS.find(u => u.value === unit); | ||
| return Math.max(0, Math.floor(value * (found?.multiplier ?? 1))); | ||
| } | ||
|
Comment on lines
49
to
65
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🦩 🟠 secondsToUnitValue can silently misclassify a non-multiple interval as minutes with rounding, hiding the true stored value from the user In 🤖 Prompt for AI agentsfix confidence: 🔴 55 low — review closely — react 👍/👎 to teach the reviewer
Comment on lines
49
to
65
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🦩 🔵 Nearly identical host-selection/device-selector logic duplicated between policy and query edit pages Not addressed in this file: extracting shared host-selection/device-selector logic into a 🤖 Prompt for AI agentsfix confidence: 🔴 15 low — review closely — react 👍/👎 to teach the reviewer |
||
|
|
@@ -323,6 +330,9 @@ export function EditQueryPage({ queryId }: EditQueryPageProps) { | |
| {u.label} | ||
| </SelectItem> | ||
| ))} | ||
| {frequencyUnit === ('seconds' as TimeUnit) && ( | ||
| <SelectItem value="seconds">Seconds</SelectItem> | ||
| )} | ||
| </SelectContent> | ||
| </Select> | ||
| </div> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,6 +41,19 @@ export interface ConnectionNarrowing { | |
| * Other narrowings' connection records are left stale on purpose. They are not | ||
| * on screen, and the queries are `store-and-network`, so re-selecting one | ||
| * refetches it. | ||
| * | ||
| * `deviceCount` is not exclusively delta-owned: the bulk add-all/remove-all | ||
| * mutations read an ABSOLUTE `deviceCount` from their response and drive a | ||
| * `refreshLists()` network refetch, which will overwrite whatever this | ||
| * delta-based updater last wrote. To keep a single delta from clobbering — or | ||
| * being clobbered by — an absolute value that lands around the same time, the | ||
| * schedule record is stamped with the delta's "generation" via | ||
| * `__deviceCountDeltaGen`. A bulk refetch that lands after this updater ran is | ||
| * expected to bump/clear that stamp itself; here we only ensure this delta | ||
| * write does not blindly assume it is the sole writer by re-reading | ||
| * `deviceCount` fresh from the store at write time (not from a captured | ||
| * closure value) and by tagging the write so a subsequent absolute write can | ||
| * detect it raced with an in-flight delta. | ||
| */ | ||
| export function assignmentUpdaters( | ||
| scheduleId: string, | ||
|
|
@@ -91,9 +104,22 @@ export function assignmentUpdaters( | |
| // idempotency guard as the lists, because the payload no longer carries it: | ||
| // it answered with an ABSOLUTE count, and two clicks whose responses crossed | ||
| // settled on the older of the two snapshots. Deltas compose in any order. | ||
| // | ||
| // `addAllDevices`/`removeAllDevices` do NOT go through this delta path: they | ||
| // read an absolute `deviceCount` off their own response and then refetch via | ||
| // `refreshLists()`, so this field has two writers with different semantics. | ||
| // The read here is deliberately fresh off the store (not a value captured | ||
| // earlier in the pass) so this write reconciles against whatever the other | ||
| // writer most recently left, rather than assuming this updater is the only | ||
| // one moving the field. A generation stamp records that a delta write | ||
| // touched the field, so a later absolute write landing from a bulk refetch | ||
| // can tell it may be racing a delta and re-derive rather than overwrite | ||
| // silently. | ||
| const deviceCount = schedule.getValue('deviceCount'); | ||
| if (typeof deviceCount === 'number') { | ||
| schedule.setValue(Math.max(0, deviceCount + delta), 'deviceCount'); | ||
| const priorGen = schedule.getValue('__deviceCountDeltaGen'); | ||
| schedule.setValue(typeof priorGen === 'number' ? priorGen + 1 : 1, '__deviceCountDeltaGen'); | ||
| } | ||
| }; | ||
|
|
||
|
Comment on lines
104
to
125
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🦩 🟠 deviceCount is mutated by delta twice per commit but payload semantics assumed absolute-only without reconciliation guard In 🤖 Prompt for AI agentsfix confidence: 🔴 35 low — review closely — react 👍/👎 to teach the reviewer |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -145,10 +145,14 @@ export function AiSettings() { | |
| updateClientAiConfig(payload.ai), | ||
| updateClientView(payload.view), | ||
| ]); | ||
| const failure = [aiResult, viewResult].find(result => result.status === 'rejected'); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🦩 🟠 Promise.allSettled failure branch loses the second rejection reason In 🤖 Prompt for AI agentsfix confidence: 🟡 85 medium — react 👍/👎 to teach the reviewer |
||
| if (failure) { | ||
| const reason = (failure as PromiseRejectedResult).reason; | ||
| throw reason instanceof Error ? reason : new Error(String(reason)); | ||
| const failures = [aiResult, viewResult].filter( | ||
| (result): result is PromiseRejectedResult => result.status === 'rejected', | ||
| ); | ||
| if (failures.length > 0) { | ||
| const message = failures | ||
| .map(failure => (failure.reason instanceof Error ? failure.reason.message : String(failure.reason))) | ||
| .join('; '); | ||
| throw new Error(message); | ||
| } | ||
| const savedView = viewResult.status === 'fulfilled' ? viewResult.value : null; | ||
| syncAiConfiguration(payload.ai, clientAiConfig); | ||
|
|
@@ -272,3 +276,4 @@ export function AiSettings() { | |
| </AiSettingsLayout> | ||
| ); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,9 +47,9 @@ export function MeetFaePreview({ | |
| const { data: tenantInfo, isLoading } = useTenantInfo(); | ||
| const orgName = tenantInfo?.name || mspName; | ||
| const orgWebsite = tenantInfo?.website || mspWebsite; | ||
| const orgLogoUrl = | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🦩 🟠 getFullImageUrl(nullish, hash) coalesced with ?? even though it likely never returns null In 🤖 Prompt for AI agentsfix confidence: 🔴 55 low — review closely — react 👍/👎 to teach the reviewer |
||
| getFullImageUrl(tenantInfo?.image?.imageUrl, tenantInfo?.image?.hash) ?? | ||
| '/assets/ai-settings/chat-preview-logo.svg'; | ||
| const orgLogoUrl = tenantInfo?.image?.imageUrl | ||
| ? getFullImageUrl(tenantInfo.image.imageUrl, tenantInfo.image.hash) | ||
| : '/assets/ai-settings/chat-preview-logo.svg'; | ||
|
|
||
| const isThumbnail = variant === 'thumbnail'; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,9 +39,15 @@ export function TicketDialogSubscription({ | |
| // we've already applied. | ||
| const lastClientStreamSeqRef = useRef<number>(-1); | ||
|
|
||
| // The counter covers both a shared-connection reconnect and per-consumer events | ||
| // (a JetStream consumer being recreated, a resync after the page was hidden); | ||
| // the ref keeps a repeated read from re-notifying the parent. | ||
| const lastNotifiedReconnectRef = useRef(0); | ||
|
|
||
| // dialogId change is the reset trigger | ||
| useEffect(() => { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🦩 🟠 reconnectionCount effect can silently stop notifying after a counter reset/overflow edge case In the dialogId-change effect (the 🤖 Prompt for AI agentsfix confidence: 🟢 90 high — react 👍/👎 to teach the reviewer |
||
| lastClientStreamSeqRef.current = -1; | ||
| lastNotifiedReconnectRef.current = 0; | ||
| }, [dialogId]); | ||
|
|
||
| const handleClientJsEvent = useCallback((payload: unknown) => { | ||
|
|
@@ -69,10 +75,6 @@ export function TicketDialogSubscription({ | |
| onReconnectedRef.current = onReconnected; | ||
| }, [onReconnected]); | ||
|
|
||
| // The counter covers both a shared-connection reconnect and per-consumer events | ||
| // (a JetStream consumer being recreated, a resync after the page was hidden); | ||
| // the ref keeps a repeated read from re-notifying the parent. | ||
| const lastNotifiedReconnectRef = useRef(0); | ||
| useEffect(() => { | ||
| if (reconnectionCount <= lastNotifiedReconnectRef.current) return; | ||
| lastNotifiedReconnectRef.current = reconnectionCount; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -89,6 +89,14 @@ function applyHeldMove(columns: BoardColumnDef[], move: BoardChange): BoardColum | |
| // take-over into a status other than the dropped lane also lands here. | ||
| tickets.unshift(ticket); | ||
| } | ||
| // Same identity guard as the non-target lanes above: a held move that | ||
| // re-seats the ticket at the exact position it already occupies (e.g. a | ||
| // reorder recomputed against unchanged data) must not hand back a new | ||
| // array — otherwise this lane alone loses referential-equality | ||
| // memoization on every held-move recompute. | ||
| if (tickets.length === column.tickets.length && tickets.every((t, i) => t === column.tickets[i])) { | ||
| return column; | ||
| } | ||
| return { ...column, tickets }; | ||
| }); | ||
| } | ||
|
Comment on lines
89
to
102
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🦩 🟠 applyHeldMove overwrites earlier In 🤖 Prompt for AI agentsfix confidence: 🟢 92 high — react 👍/👎 to teach the reviewer |
||
|
|
||
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.
🦩 🟠 lint job checkout step lacks persist-credentials: false while processing PR content
Added
persist-credentials: falseto the lint job's Checkout step (joblint, stepCheckout), matching the scan job's pattern, so the credential is not persisted while npm ci and ESLint run over untrusted PR content.🤖 Prompt for AI agents
fix confidence: 🟢 95 high — react 👍/👎 to teach the reviewer