Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 1 addition & 22 deletions src/features/bulk-actions-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import React from 'react'
import { Box, Spinner } from '@primer/react'
import type { BulkEditRelationshipsUpdate, IssueSearchResultData } from '@/lib/messages'
import type { RelationshipKey, RelationshipSelectionState } from '@/features/bulk-edit-utils'
import type { RelationshipKey } from '@/features/bulk-edit-utils'
import { isMac } from '@/lib/keyboard'
import { Z_MODAL } from '@/lib/z-index'

Expand All @@ -27,27 +27,6 @@ export function createEmptyRelationshipUpdates(): BulkEditRelationshipsUpdate {
}
}

export function createEmptyRelationshipSelection(): RelationshipSelectionState {
return {
parent: false,
blockedBy: false,
blocking: false,
}
}

export function hasRelationshipOperations(relationships: BulkEditRelationshipsUpdate): boolean {
return Boolean(
relationships.parent.clear ||
relationships.parent.set ||
relationships.blockedBy.clear ||
relationships.blockedBy.add.length > 0 ||
relationships.blockedBy.remove.length > 0 ||
relationships.blocking.clear ||
relationships.blocking.add.length > 0 ||
relationships.blocking.remove.length > 0,
)
}

export type ParentOperation = 'set' | 'clear'
export type ListOperation = 'add' | 'remove' | 'clear'

Expand Down
55 changes: 1 addition & 54 deletions src/features/bulk-duplicate-utils.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// types, constants, and pure helpers for the bulk-duplicate modal.

import React from 'react'
import type { IssueRelationshipData, ItemPreviewData } from '@/lib/messages'
import type { ItemPreviewData } from '@/lib/messages'
import { primerCss } from '@/lib/primer-css-helper'
import {
AlertIcon,
Expand All @@ -14,7 +14,6 @@ import {
SyncIcon,
TextLineIcon,
} from '@/ui/icons'
import { formatIssueReference } from '@/lib/relationship-utils'

/**
* §11.2 — three-stage state machine collapsed to two:
Expand Down Expand Up @@ -49,12 +48,6 @@ export interface DuplicateSection {
helperText?: string
}

export interface ReviewRow {
id: SectionId
label: string
value: string
}

export const TITLE_SECTION_ID = 'TITLE' as const
export const BODY_SECTION_ID = 'BODY' as const
export const ASSIGNEES_SECTION_ID = 'ASSIGNEES' as const
Expand Down Expand Up @@ -136,52 +129,6 @@ export function duplicateValueTooltip(fieldName: string): string {
return `Value applied to the duplicated item for ${fieldName}.`
}

export function formatIssueSummary(issue: IssueRelationshipData): string {
return `${formatIssueReference(issue)} — ${issue.title}`
}

export function summarizeText(value: string, fallback = 'Empty'): string {
const trimmed = value.trim()
if (!trimmed) return fallback
return trimmed.length > 80 ? `${trimmed.slice(0, 77)}…` : trimmed
}

export function summarizeIssueList(issues: IssueRelationshipData[]): string {
if (issues.length === 0) return 'Skipped'
const preview = issues.slice(0, 2).map(formatIssueSummary).join('; ')
return issues.length > 2 ? `${preview} +${issues.length - 2} more` : preview
}

export function summarizeFieldValue(field: EditableField): string {
if (field.dataType === 'TEXT') {
return summarizeText(field.text ?? '', 'None / Cleared')
}

if (field.dataType === 'SINGLE_SELECT') {
return field.optionName || 'None / Cleared'
}

if (field.dataType === 'ITERATION') {
return field.iterationTitle || 'None / Cleared'
}

if (field.dataType === 'NUMBER') {
return field.number === undefined || field.number === null
? 'None / Cleared'
: String(field.number)
}

if (field.dataType === 'DATE') {
if (!field.date) return 'None / Cleared'
const parsed = new Date(`${field.date}T00:00:00`)
return Number.isNaN(parsed.getTime())
? field.date
: parsed.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })
}

return 'None / Cleared'
}

/**
* §11.6 — Diff predicate per section. Compares the user-edited value against
* the source preview value and returns `true` when they differ. The REVIEW
Expand Down
113 changes: 0 additions & 113 deletions src/features/bulk-edit-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
CalendarIcon,
HashIcon,
ArrowRightIcon,
ListCheckIcon,
OptionsSelectIcon,
PencilIcon,
PersonIcon,
Expand All @@ -14,8 +13,6 @@ import {
TagIcon,
TextLineIcon,
} from '@/ui/icons'
import type { BulkEditRelationshipsUpdate, IssueRelationshipData } from '@/lib/messages'
import { formatIssueReference } from '@/lib/relationship-utils'

export interface ProjectField {
id: string
Expand All @@ -39,15 +36,8 @@ export interface RelationshipSelectionState {
blocking: boolean
}

export type WizardStep = 'TOKEN_WARNING' | 'FIELDS' | 'VALUES' | 'SUMMARY'

export type RelationshipKey = keyof RelationshipSelectionState

export type RelationshipSummaryRow = {
label: string
value: string
}

/** Project V2 field types editable via the field picker (not issue properties). */
export const EDITABLE_PROJECT_FIELD_DATATYPES = new Set([
'TEXT',
Expand Down Expand Up @@ -221,106 +211,3 @@ export function getFieldIcon(dataType: string): React.ReactNode {
return null
}
}

export function getFieldSelectionTooltip(field: ProjectField): string {
return `Select ${field.name} to set the same value across all selected items.`
}

export function getFieldValueStepTooltip(field: ProjectField, itemCount: number): string {
return `Set ${field.name} for all ${itemCount} selected item${itemCount !== 1 ? 's' : ''}.`
}

export function getRelationshipSelectionTooltip(label: string): string {
return `Enable ${label.toLowerCase()} relationship changes for this bulk edit.`
}

export function issueTitle(issue: IssueRelationshipData): string {
return issue.title.trim() || formatIssueReference(issue)
}

export function getRelationshipSelectionCount(selection: RelationshipSelectionState): number {
return Object.values(selection).filter(Boolean).length
}

export function buildRelationshipSummaryRows(
relationships: BulkEditRelationshipsUpdate,
): RelationshipSummaryRow[] {
const rows: RelationshipSummaryRow[] = []

if (relationships.parent.clear) {
rows.push({ label: 'Parent', value: 'Clear existing parent relationship' })
}
if (relationships.parent.set) {
rows.push({
label: 'Parent',
value: `Set to ${formatIssueReference(relationships.parent.set)}`,
})
}

if (relationships.blockedBy.clear) {
rows.push({ label: 'Blocked by', value: 'Clear all current blockers' })
}
if (relationships.blockedBy.add.length > 0) {
rows.push({
label: 'Blocked by',
value: `Add ${relationships.blockedBy.add.map(formatIssueReference).join(', ')}`,
})
}
if (relationships.blockedBy.remove.length > 0) {
rows.push({
label: 'Blocked by',
value: `Remove ${relationships.blockedBy.remove.map(formatIssueReference).join(', ')}`,
})
}

if (relationships.blocking.clear) {
rows.push({ label: 'Blocking', value: 'Clear all currently blocked issues' })
}
if (relationships.blocking.add.length > 0) {
rows.push({
label: 'Blocking',
value: `Add ${relationships.blocking.add.map(formatIssueReference).join(', ')}`,
})
}
if (relationships.blocking.remove.length > 0) {
rows.push({
label: 'Blocking',
value: `Remove ${relationships.blocking.remove.map(formatIssueReference).join(', ')}`,
})
}

return rows
}

export function describeFieldValue(
field: ProjectField,
fieldValues: Record<string, unknown>,
): string {
const valueObj = (fieldValues[field.id] || {}) as Record<string, unknown>
let displayValue = 'None / Cleared'

const arr = valueObj.array as { name: string }[] | undefined
if (arr && arr.length > 0) {
displayValue = arr.map((v) => v.name).join(', ')
} else if (valueObj.date) {
displayValue = new Date((valueObj.date as string) + 'T00:00:00').toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric',
})
} else if (valueObj.number !== undefined && valueObj.number !== null) {
displayValue = String(valueObj.number)
} else if (valueObj.text) {
displayValue = valueObj.text as string
} else if (valueObj.singleSelectOptionId && field.options) {
const opt = field.options.find((option) => option.id === valueObj.singleSelectOptionId)
if (opt) displayValue = opt.name
} else if (valueObj.iterationId && field.configuration?.iterations) {
const iter = field.configuration.iterations.find((option) => option.id === valueObj.iterationId)
if (iter) displayValue = iter.title
}

return displayValue
}

export const bulkEditHeaderIcon = <ListCheckIcon size={16} />
2 changes: 0 additions & 2 deletions src/features/bulk-random-assign-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,6 @@ export function distributeRoundRobin(items: string[], assignees: string[]): Map<

export type DistributionStrategy = 'balanced' | 'random' | 'round-robin'

export type DistributionFunction = (items: string[], assignees: string[]) => Map<string, string[]>

/** Inverts assignee→itemIds to itemId→assigneeIds. */
export function invertDistribution(distribution: Map<string, string[]>): Map<string, string[]> {
const byItem = new Map<string, string[]>()
Expand Down
10 changes: 0 additions & 10 deletions src/lib/checkbox-portal-store.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import { Effect, Stream, SubscriptionRef } from 'effect'

export type PortalEntry =
| { type: 'row'; container: HTMLElement; itemId: string }
| { type: 'group'; container: HTMLElement; getItemIds: () => string[] }
| { type: 'selectall'; container: HTMLElement }

type Listener = (entries: readonly PortalEntry[]) => void

const _ref = Effect.runSync(
SubscriptionRef.make<readonly PortalEntry[]>([] as readonly PortalEntry[]),
)
let current: readonly PortalEntry[] = []

const listeners = new Set<Listener>()
Expand All @@ -20,7 +15,6 @@ function isConnected(entry: PortalEntry): boolean {

function setState(next: readonly PortalEntry[]): void {
current = next
Effect.runSync(SubscriptionRef.set(_ref, next))
listeners.forEach((fn) => fn(current))
}

Expand Down Expand Up @@ -65,7 +59,3 @@ export const checkboxPortalStore = {
return () => listeners.delete(fn)
},
}

export const checkboxPortalChanges: Stream.Stream<readonly PortalEntry[]> = _ref.changes

export const getCheckboxPortalSnapshot = (): readonly PortalEntry[] => current
7 changes: 0 additions & 7 deletions src/lib/effect-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,3 @@ export const runHandler = <A, E>(label: string, effect: Effect.Effect<A, E, neve
),
),
)

/**
* Releases the underlying runtime and finalizes scoped resources.
* Mainly useful from tests (`afterAll`) — the SW lifecycle implicitly tears
* the runtime down when the worker is terminated.
*/
export const disposeRuntime = (): Promise<void> => AppRuntime.dispose()
22 changes: 0 additions & 22 deletions src/lib/graphql-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,28 +377,6 @@ export const GET_VIEWER_REPOS_PAGE = `
}
`

export const SEARCH_OWNER_REPOS = `
query SearchOwnerRepos($login: String!, $query: String!, $first: Int!) {
repositoryOwner(login: $login) {
repositories(
first: $first
query: $query
orderBy: { field: PUSHED_AT, direction: DESC }
) {
nodes {
id
name
nameWithOwner
isPrivate
description
hasIssuesEnabled
isArchived
}
}
}
}
`

export const GET_POSSIBLE_TRANSFER_REPOS = `
query GetPossibleTransferRepos($issueId: ID!, $first: Int!) {
node(id: $issueId) {
Expand Down
5 changes: 0 additions & 5 deletions src/lib/hovercard-portal-target.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,3 @@ export function getHovercardAppendTarget(): HTMLElement {
export function setHovercardAppendTarget(target: HTMLElement | null): void {
hovercardAppendTarget = target
}

/** @internal test helper */
export function resetHovercardAppendTargetForTests(): void {
hovercardAppendTarget = null
}
10 changes: 1 addition & 9 deletions src/lib/queue-store.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Duration, Effect, Fiber, Stream, SubscriptionRef } from 'effect'
import { Duration, Effect, Fiber } from 'effect'

import { onMessage } from '@/lib/messages'
import { toastStore } from '@/lib/toast-store'
Expand Down Expand Up @@ -58,7 +58,6 @@ type Listener = (entries: ProcessEntry[]) => void
const DISMISS_DELAY = Duration.millis(3000)
const DEFAULT_UNDO_WINDOW_MS = 10_000

const _ref = Effect.runSync(SubscriptionRef.make<ReadonlyMap<string, ProcessEntry>>(new Map()))
let processes: Map<string, ProcessEntry> = new Map()
const listeners = new Set<Listener>()
const dismissTimers = new Map<string, Fiber.RuntimeFiber<void>>()
Expand Down Expand Up @@ -145,7 +144,6 @@ function withPhase(entry: Omit<ProcessEntry, 'phase'>): ProcessEntry {

function setState(next: Map<string, ProcessEntry>): void {
processes = next
Effect.runSync(SubscriptionRef.set(_ref, next))
const entries = Array.from(next.values())
listeners.forEach((fn) => fn(entries))
}
Expand Down Expand Up @@ -229,12 +227,6 @@ export const queueStore = {
},
}

export const queueChanges: Stream.Stream<ReadonlyMap<string, ProcessEntry>> = _ref.changes

// return a defensive copy so external consumers cannot mutate the live ref
// and bypass setState's notify/SubscriptionRef updates.
export const getQueueSnapshot = (): ReadonlyMap<string, ProcessEntry> => new Map(processes)

// single central listener for the whole CS context
onMessage('queueStateUpdate', ({ data }) => {
// use processId if provided, fall back to sentinel 'bulk' for legacy bulk-update path
Expand Down
2 changes: 0 additions & 2 deletions src/lib/schemas-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -592,5 +592,3 @@ export const Messages = {
output: Schema.Void,
},
} as const

export type MessageKey = keyof typeof Messages
Loading
Loading