← back to kjm99d__MonkeyPlanner

Function bodies 254 total

All specs Real LLM only Function bodies
Delete method · go · L102-L112 (11 LOC)
backend/internal/storage/sqlite/webhook_repo.go
func (r *webhookRepo) Delete(ctx context.Context, id string) error {
	res, err := r.db.ExecContext(ctx, `DELETE FROM webhooks WHERE id = ?`, id)
	if err != nil {
		return err
	}
	n, _ := res.RowsAffected()
	if n == 0 {
		return storage.ErrNotFound
	}
	return nil
}
getByID method · go · L114-L118 (5 LOC)
backend/internal/storage/sqlite/webhook_repo.go
func (r *webhookRepo) getByID(ctx context.Context, id string) (domain.Webhook, error) {
	row := r.db.QueryRowContext(ctx,
		`SELECT id, board_id, name, url, events, enabled, created_at FROM webhooks WHERE id = ?`, id)
	return scanWebhook(row)
}
scanWebhook function · go · L120-L138 (19 LOC)
backend/internal/storage/sqlite/webhook_repo.go
func scanWebhook(row interface{ Scan(...any) error }) (domain.Webhook, error) {
	var wh domain.Webhook
	var evts string
	var enabled int
	err := row.Scan(&wh.ID, &wh.BoardID, &wh.Name, &wh.URL, &evts, &enabled, &wh.CreatedAt)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return domain.Webhook{}, storage.ErrNotFound
		}
		return domain.Webhook{}, err
	}
	_ = json.Unmarshal([]byte(evts), &wh.Events)
	if wh.Events == nil {
		wh.Events = []domain.WebhookEvent{}
	}
	wh.Enabled = enabled != 0
	wh.CreatedAt = wh.CreatedAt.UTC()
	return wh, nil
}
scanWebhooks function · go · L140-L150 (11 LOC)
backend/internal/storage/sqlite/webhook_repo.go
func scanWebhooks(rows *sql.Rows) ([]domain.Webhook, error) {
	var out []domain.Webhook
	for rows.Next() {
		wh, err := scanWebhook(rows)
		if err != nil {
			return nil, err
		}
		out = append(out, wh)
	}
	return out, rows.Err()
}
Dist function · go · L12-L14 (3 LOC)
backend/web/embed_dev.go
func Dist() (fs.FS, error) {
	return nil, errors.New("web.Dist: dev build does not embed frontend (use Vite dev server on :5173)")
}
Dist function · go · L14-L16 (3 LOC)
backend/web/embed_prod.go
func Dist() (fs.FS, error) {
	return fs.Sub(embeddedDist, "dist")
}
enableAxe function · typescript · L4-L10 (7 LOC)
frontend/src/a11y/axe.ts
export async function enableAxe() {
  if (!import.meta.env.DEV) return;
  const React = await import('react');
  const ReactDOM = await import('react-dom');
  const axe = (await import('@axe-core/react')).default;
  axe(React, ReactDOM, 1000);
}
Same scanner, your repo: https://repobility.com — Repobility
axe function · typescript · L4-L20 (17 LOC)
frontend/src/a11y/jest-axe-lite.ts
export async function axe(container: Element): Promise<AxeResults> {
  return new Promise<AxeResults>((resolve, reject) => {
    axeCore.run(
      container,
      {
        rules: {
          // jsdom 에서 색상 대비 검사 신뢰할 수 없음; dev 런타임 axe가 별도 감사.
          'color-contrast': { enabled: false },
        },
      },
      (err, results) => {
        if (err) reject(err);
        else resolve(results);
      },
    );
  });
}
request function · typescript · L9-L28 (20 LOC)
frontend/src/api/client.ts
async function request<T>(method: string, path: string, body?: unknown): Promise<T> {
  const res = await fetch(path, {
    method,
    headers: body !== undefined ? { 'Content-Type': 'application/json' } : undefined,
    body: body !== undefined ? JSON.stringify(body) : undefined,
  });
  if (!res.ok) {
    const err: ApiError = { status: res.status };
    try {
      const parsed = (await res.json()) as { error?: { code?: string; message?: string } };
      err.code = parsed.error?.code;
      err.message = parsed.error?.message;
    } catch {
      /* ignore */
    }
    throw err;
  }
  if (res.status === 204) return undefined as T;
  return (await res.json()) as T;
}
useBoards function · typescript · L14-L20 (7 LOC)
frontend/src/api/hooks.ts
export function useBoards(options?: Partial<UseQueryOptions<Board[]>>) {
  return useQuery<Board[]>({
    queryKey: boardsKey,
    queryFn: () => api.get<Board[]>('/api/boards'),
    ...options,
  });
}
useCreateBoard function · typescript · L22-L31 (10 LOC)
frontend/src/api/hooks.ts
export function useCreateBoard() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (p: { name: string; viewType?: 'kanban' | 'list' }) =>
      api.post<Board>('/api/boards', p),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: boardsKey });
    },
  });
}
useDeleteBoard function · typescript · L33-L41 (9 LOC)
frontend/src/api/hooks.ts
export function useDeleteBoard() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (id: string) => api.del<void>(`/api/boards/${id}`),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: boardsKey });
    },
  });
}
issuesKey function · typescript · L45-L51 (7 LOC)
frontend/src/api/hooks.ts
export function issuesKey(filter?: {
  boardId?: string;
  status?: IssueStatus;
  parentId?: string | null;
}) {
  return ['issues', filter ?? {}] as const;
}
useIssues function · typescript · L53-L67 (15 LOC)
frontend/src/api/hooks.ts
export function useIssues(filter?: {
  boardId?: string;
  status?: IssueStatus;
  parentId?: string | null;
}) {
  const qs = new URLSearchParams();
  if (filter?.boardId) qs.set('board_id', filter.boardId);
  if (filter?.status) qs.set('status', filter.status);
  if (filter?.parentId !== undefined)
    qs.set('parent_id', filter.parentId === null ? '' : filter.parentId);
  return useQuery<Issue[]>({
    queryKey: issuesKey(filter),
    queryFn: () => api.get<Issue[]>(`/api/issues?${qs.toString()}`),
  });
}
useIssue function · typescript · L69-L75 (7 LOC)
frontend/src/api/hooks.ts
export function useIssue(id: string | undefined) {
  return useQuery<{ issue: Issue; children: Issue[] }>({
    queryKey: ['issue', id],
    queryFn: () => api.get(`/api/issues/${id}`),
    enabled: !!id,
  });
}
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
useCreateIssue function · typescript · L77-L87 (11 LOC)
frontend/src/api/hooks.ts
export function useCreateIssue() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (p: { boardId: string; title: string; body?: string; parentId?: string }) =>
      api.post<Issue>('/api/issues', p),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['issues'] });
      qc.invalidateQueries({ queryKey: ['calendar'] });
    },
  });
}
useUpdateIssue function · typescript · L89-L107 (19 LOC)
frontend/src/api/hooks.ts
export function useUpdateIssue() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: ({
      id,
      patch,
    }: {
      id: string;
      patch: Partial<Pick<Issue, 'title' | 'body' | 'instructions' | 'status' | 'criteria'>> & {
        parentId?: string | null;
      };
    }) => api.patch<Issue>(`/api/issues/${id}`, patch),
    onSuccess: (_data, vars) => {
      qc.invalidateQueries({ queryKey: ['issues'] });
      qc.invalidateQueries({ queryKey: ['issue', vars.id] });
      qc.invalidateQueries({ queryKey: ['calendar'] });
    },
  });
}
useApproveIssue function · typescript · L109-L119 (11 LOC)
frontend/src/api/hooks.ts
export function useApproveIssue() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (id: string) => api.post<Issue>(`/api/issues/${id}/approve`),
    onSuccess: (_data, id) => {
      qc.invalidateQueries({ queryKey: ['issues'] });
      qc.invalidateQueries({ queryKey: ['issue', id] });
      qc.invalidateQueries({ queryKey: ['calendar'] });
    },
  });
}
useDeleteIssue function · typescript · L121-L130 (10 LOC)
frontend/src/api/hooks.ts
export function useDeleteIssue() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (id: string) => api.del<void>(`/api/issues/${id}`),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['issues'] });
      qc.invalidateQueries({ queryKey: ['calendar'] });
    },
  });
}
useBoardProperties function · typescript · L134-L140 (7 LOC)
frontend/src/api/hooks.ts
export function useBoardProperties(boardId: string | undefined) {
  return useQuery<BoardProperty[]>({
    queryKey: ['boardProperties', boardId],
    queryFn: () => api.get<BoardProperty[]>(`/api/boards/${boardId}/properties`),
    enabled: !!boardId,
  });
}
useCreateBoardProperty function · typescript · L142-L151 (10 LOC)
frontend/src/api/hooks.ts
export function useCreateBoardProperty() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (p: { boardId: string; name: string; type: PropertyType; options?: string[] }) =>
      api.post<BoardProperty>(`/api/boards/${p.boardId}/properties`, p),
    onSuccess: (_d, v) => {
      qc.invalidateQueries({ queryKey: ['boardProperties', v.boardId] });
    },
  });
}
useDeleteBoardProperty function · typescript · L153-L162 (10 LOC)
frontend/src/api/hooks.ts
export function useDeleteBoardProperty() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: ({ boardId, propId }: { boardId: string; propId: string }) =>
      api.del<void>(`/api/boards/${boardId}/properties/${propId}`),
    onSuccess: (_d, v) => {
      qc.invalidateQueries({ queryKey: ['boardProperties', v.boardId] });
    },
  });
}
useUpdateIssueProperties function · typescript · L164-L174 (11 LOC)
frontend/src/api/hooks.ts
export function useUpdateIssueProperties() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: ({ id, properties }: { id: string; properties: Record<string, unknown> }) =>
      api.patch<Issue>(`/api/issues/${id}`, { properties }),
    onSuccess: (_d, v) => {
      qc.invalidateQueries({ queryKey: ['issue', v.id] });
      qc.invalidateQueries({ queryKey: ['issues'] });
    },
  });
}
Methodology: Repobility · https://repobility.com/research/state-of-ai-code-2026/
useWebhooks function · typescript · L178-L184 (7 LOC)
frontend/src/api/hooks.ts
export function useWebhooks(boardId: string | undefined) {
  return useQuery<Webhook[]>({
    queryKey: ['webhooks', boardId],
    queryFn: () => api.get<Webhook[]>(`/api/boards/${boardId}/webhooks`),
    enabled: !!boardId,
  });
}
useCreateWebhook function · typescript · L186-L195 (10 LOC)
frontend/src/api/hooks.ts
export function useCreateWebhook() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (p: { boardId: string; name: string; url: string; events: WebhookEvent[] }) =>
      api.post<Webhook>(`/api/boards/${p.boardId}/webhooks`, p),
    onSuccess: (_d, v) => {
      qc.invalidateQueries({ queryKey: ['webhooks', v.boardId] });
    },
  });
}
useDeleteWebhook function · typescript · L197-L206 (10 LOC)
frontend/src/api/hooks.ts
export function useDeleteWebhook() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: ({ boardId, whId }: { boardId: string; whId: string }) =>
      api.del<void>(`/api/boards/${boardId}/webhooks/${whId}`),
    onSuccess: (_d, v) => {
      qc.invalidateQueries({ queryKey: ['webhooks', v.boardId] });
    },
  });
}
useMonthStats function · typescript · L210-L215 (6 LOC)
frontend/src/api/hooks.ts
export function useMonthStats(year: number, month: number) {
  return useQuery<DayCount[]>({
    queryKey: ['calendar', 'month', year, month],
    queryFn: () => api.get<DayCount[]>(`/api/calendar?year=${year}&month=${month}`),
  });
}
useDayStats function · typescript · L217-L223 (7 LOC)
frontend/src/api/hooks.ts
export function useDayStats(date: string | undefined) {
  return useQuery<DayStats>({
    queryKey: ['calendar', 'day', date],
    queryFn: () => api.get<DayStats>(`/api/calendar/day?date=${date}`),
    enabled: !!date,
  });
}
useAddDependency function · typescript · L227-L236 (10 LOC)
frontend/src/api/hooks.ts
export function useAddDependency() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: ({ issueId, blockerId }: { issueId: string; blockerId: string }) =>
      api.post<void>(`/api/issues/${issueId}/dependencies`, { blockerId }),
    onSuccess: (_data, vars) => {
      qc.invalidateQueries({ queryKey: ['issue', vars.issueId] });
    },
  });
}
useRemoveDependency function · typescript · L238-L247 (10 LOC)
frontend/src/api/hooks.ts
export function useRemoveDependency() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: ({ issueId, blockerId }: { issueId: string; blockerId: string }) =>
      api.del<void>(`/api/issues/${issueId}/dependencies/${blockerId}`),
    onSuccess: (_data, vars) => {
      qc.invalidateQueries({ queryKey: ['issue', vars.issueId] });
    },
  });
}
useReorderIssues function · typescript · L251-L260 (10 LOC)
frontend/src/api/hooks.ts
export function useReorderIssues() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: ({ boardId, issueIds }: { boardId: string; issueIds: string[] }) =>
      api.post<void>(`/api/boards/${boardId}/issues/reorder`, { issueIds }),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['issues'] });
    },
  });
}
Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
useComments function · typescript · L264-L270 (7 LOC)
frontend/src/api/hooks.ts
export function useComments(issueId: string | undefined) {
  return useQuery<Comment[]>({
    queryKey: ['comments', issueId],
    queryFn: () => api.get<Comment[]>(`/api/issues/${issueId}/comments`),
    enabled: !!issueId,
  });
}
useCreateComment function · typescript · L272-L281 (10 LOC)
frontend/src/api/hooks.ts
export function useCreateComment() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: ({ issueId, body }: { issueId: string; body: string }) =>
      api.post<Comment>(`/api/issues/${issueId}/comments`, { body }),
    onSuccess: (_data, vars) => {
      qc.invalidateQueries({ queryKey: ['comments', vars.issueId] });
    },
  });
}
useDeleteComment function · typescript · L283-L292 (10 LOC)
frontend/src/api/hooks.ts
export function useDeleteComment() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: ({ commentId, issueId: _issueId }: { issueId: string; commentId: string }) =>
      api.del<void>(`/api/comments/${commentId}`),
    onSuccess: (_data, vars) => {
      qc.invalidateQueries({ queryKey: ['comments', vars.issueId] });
    },
  });
}
App function · typescript · L10-L24 (15 LOC)
frontend/src/App.tsx
export default function App() {
  return (
    <Routes>
      <Route element={<Layout />}>
        <Route path="/" element={<HomePage />} />
        <Route path="/boards" element={<BoardsListPage />} />
        <Route path="/boards/:boardId" element={<BoardPage />} />
        <Route path="/issues/:issueId" element={<IssuePage />} />
        <Route path="/calendar" element={<CalendarPage />} />
        <Route path="/approve" element={<ApprovalPage />} />
        <Route path="*" element={<NotFound />} />
      </Route>
    </Routes>
  );
}
NotFound function · typescript · L26-L33 (8 LOC)
frontend/src/App.tsx
function NotFound() {
  return (
    <section>
      <h1 className="text-3xl font-bold">찾을 수 없습니다</h1>
      <p className="mt-2 text-ink-secondary">요청하신 경로가 존재하지 않습니다.</p>
    </section>
  );
}
Breadcrumb function · typescript · L6-L24 (19 LOC)
frontend/src/components/Breadcrumb.tsx
export function Breadcrumb({ items }: { items: Crumb[] }) {
  const { t } = useTranslation();
  return (
    <nav aria-label={t('common.location')} className="flex items-center gap-1 text-sm text-ink-muted">
      {items.map((item, i) => (
        <span key={i} className="flex items-center gap-1">
          {i > 0 && <span aria-hidden>/</span>}
          {item.to ? (
            <Link to={item.to} className="hover:text-ink-primary hover:underline">
              {item.label}
            </Link>
          ) : (
            <span className="text-ink-secondary">{item.label}</span>
          )}
        </span>
      ))}
    </nav>
  );
}
Button function · typescript · L30-L38 (9 LOC)
frontend/src/components/Button.tsx
  function Button({ variant = 'primary', size = 'md', className = '', ...rest }, ref) {
    return (
      <button
        ref={ref}
        {...rest}
        className={`${base} ${variants[variant]} ${sizes[size]} ${className}`}
      />
    );
  },
Card function · typescript · L7-L16 (10 LOC)
frontend/src/components/Card.tsx
export function Card({ className = '', children, ...rest }: Props) {
  return (
    <div
      {...rest}
      className={`rounded-xl border border-edge-base bg-surface-subtle p-4 shadow-sm transition-all duration-200 hover:shadow-md hover:border-brand-500/40 ${className}`}
    >
      {children}
    </div>
  );
}
Same scanner, your repo: https://repobility.com — Repobility
relativeTime function · typescript · L8-L17 (10 LOC)
frontend/src/components/CommentSection.tsx
function relativeTime(dateStr: string): string {
  const now = Date.now();
  const then = new Date(dateStr).getTime();
  const diff = Math.floor((now - then) / 1000);
  if (diff < 60) return 'just now';
  if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
  if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
  if (diff < 604800) return `${Math.floor(diff / 86400)}d ago`;
  return new Date(dateStr).toLocaleDateString();
}
CommentSection function · typescript · L23-L107 (85 LOC)
frontend/src/components/CommentSection.tsx
export function CommentSection({ issueId }: Props) {
  const { t } = useTranslation();
  const { data: comments, isLoading } = useComments(issueId);
  const createComment = useCreateComment();
  const deleteComment = useDeleteComment();
  const [body, setBody] = useState('');

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    const trimmed = body.trim();
    if (!trimmed) return;
    await createComment.mutateAsync({ issueId, body: trimmed });
    setBody('');
  }

  return (
    <section aria-label={t('comments.section')} className="flex flex-col gap-4">
      <h3 className="flex items-center gap-2 text-sm font-semibold text-ink-secondary">
        <Activity size={14} />
        {t('comments.title')}
        {comments && comments.length > 0 && (
          <span className="rounded-full bg-surface-muted px-1.5 py-0.5 text-[10px] tabular-nums text-ink-muted">{comments.length}</span>
        )}
      </h3>

      {isLoading && (
        <div className="flex f
handleSubmit function · typescript · L30-L36 (7 LOC)
frontend/src/components/CommentSection.tsx
  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    const trimmed = body.trim();
    if (!trimmed) return;
    await createComment.mutateAsync({ issueId, body: trimmed });
    setBody('');
  }
ConfirmDialog function · typescript · L16-L80 (65 LOC)
frontend/src/components/ConfirmDialog.tsx
export function ConfirmDialog({
  open,
  title,
  description,
  confirmLabel,
  onConfirm,
  onCancel,
  variant = 'danger',
}: Props) {
  const { t } = useTranslation();
  const cancelRef = useRef<HTMLButtonElement>(null);

  useEffect(() => {
    if (!open) return;
    cancelRef.current?.focus();
    const handler = (e: KeyboardEvent) => {
      if (e.key === 'Escape') onCancel();
    };
    document.addEventListener('keydown', handler);
    return () => document.removeEventListener('keydown', handler);
  }, [open, onCancel]);

  if (!open) return null;

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center p-4">
      <div className="absolute inset-0 bg-black/40" onClick={onCancel} aria-hidden />
      <div
        role="alertdialog"
        aria-modal="true"
        aria-labelledby="confirm-title"
        aria-describedby="confirm-desc"
        className="relative z-10 flex w-full max-w-sm flex-col gap-4 rounded-xl border border-edge-base bg-surface-b
ContextMenu function · typescript · L17-L65 (49 LOC)
frontend/src/components/ContextMenu.tsx
export function ContextMenu({ items, position, onClose }: Props) {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!position) return;
    const handler = (e: MouseEvent) => {
      if (ref.current && !ref.current.contains(e.target as Node)) onClose();
    };
    const keyHandler = (e: KeyboardEvent) => {
      if (e.key === 'Escape') onClose();
    };
    document.addEventListener('mousedown', handler);
    document.addEventListener('keydown', keyHandler);
    return () => {
      document.removeEventListener('mousedown', handler);
      document.removeEventListener('keydown', keyHandler);
    };
  }, [position, onClose]);

  if (!position) return null;

  return (
    <div
      ref={ref}
      className="fixed z-50 min-w-[160px] rounded-lg border border-edge-base bg-surface-base py-1 shadow-lg animate-in"
      style={{ left: position.x, top: position.y }}
    >
      {items.map((item, i) => (
        item.divider ? (
          <hr key={i} className="my-1 bor
Input function · typescript · L8-L32 (25 LOC)
frontend/src/components/Input.tsx
export function Input({ label, error, id, className = '', ...rest }: Props) {
  const generatedId = useId();
  const inputId = id ?? generatedId;
  return (
    <div className="flex flex-col gap-1">
      {label && (
        <label htmlFor={inputId} className="text-sm font-medium text-ink-secondary">
          {label}
        </label>
      )}
      <input
        id={inputId}
        aria-invalid={!!error}
        aria-describedby={error ? `${inputId}-err` : undefined}
        {...rest}
        className={`h-10 rounded-md border border-edge-base bg-surface-base px-3 text-ink-primary transition-colors focus-visible:border-brand-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500/40 ${className}`}
      />
      {error && (
        <span id={`${inputId}-err`} className="text-xs text-red-600">
          {error}
        </span>
      )}
    </div>
  );
}
LanguageSwitcher function · typescript · L6-L50 (45 LOC)
frontend/src/components/LanguageSwitcher.tsx
export function LanguageSwitcher() {
  const { i18n } = useTranslation();
  const [open, setOpen] = useState(false);
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!open) return;
    const handleClickOutside = (e: MouseEvent) => {
      if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
    };
    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, [open]);

  return (
    <div className="relative" ref={ref}>
      <button
        type="button"
        onClick={() => setOpen((v) => !v)}
        className="flex h-9 w-9 items-center justify-center rounded-lg border border-edge-base text-ink-secondary transition-all duration-200 hover:bg-surface-muted hover:text-brand-500 active:scale-95"
        aria-label="Language"
      >
        <Globe size={18} />
      </button>
      {open && (
        <div className="absolute left-0 bottom-full mb-1 min-w
Layout function · typescript · L196-L314 (119 LOC)
frontend/src/components/Layout.tsx
export default function Layout() {
  const { t } = useTranslation();
  const navigate = useNavigate();
  const [sidebarOpen, setSidebarOpen] = useState(false);
  const [collapsed, setCollapsed] = useState(() => localStorage.getItem('sidebar-collapsed') === 'true');

  useEffect(() => {
    localStorage.setItem('sidebar-collapsed', String(collapsed));
  }, [collapsed]);

  useEffect(() => {
    if (!sidebarOpen) return;
    const handler = (e: KeyboardEvent) => {
      if (e.key === 'Escape') setSidebarOpen(false);
    };
    document.addEventListener('keydown', handler);
    return () => document.removeEventListener('keydown', handler);
  }, [sidebarOpen]);

  useEffect(() => {
    const handler = (e: KeyboardEvent) => {
      const tag = (e.target as HTMLElement)?.tagName;
      if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;
      if ((e.target as HTMLElement)?.isContentEditable) return;

      if (e.key === 'a' && !e.metaKey && !e.ctrlKey) {
        e.preventD
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
MarkdownEditor function · typescript · L12-L61 (50 LOC)
frontend/src/components/MarkdownEditor.tsx
export function MarkdownEditor({ value, onChange, label }: Props) {
  const { t } = useTranslation();
  const [tab, setTab] = useState<'edit' | 'preview' | 'split'>('split');
  const resolvedLabel = label ?? t('issue.body');

  return (
    <section aria-label={t('issue.body')} className="flex flex-col gap-2">
      <div className="flex items-center justify-between">
        <span className="text-sm font-medium text-ink-secondary">{resolvedLabel}</span>
        <div role="tablist" className="flex gap-1 rounded-md bg-surface-muted p-0.5 text-xs">
          {(['edit', 'split', 'preview'] as const).map((tab_) => (
            <button
              key={tab_}
              role="tab"
              aria-selected={tab === tab_}
              onClick={() => setTab(tab_)}
              className={`rounded px-2 py-1 transition-colors ${
                tab === tab_ ? 'bg-surface-base text-ink-primary shadow-sm' : 'text-ink-secondary hover:text-ink-primary'
              }`}
            >
      
PropertyEditor function · typescript · L17-L31 (15 LOC)
frontend/src/components/PropertyEditor.tsx
export function PropertyEditor({ properties, values, onChange }: Props) {
  const { t } = useTranslation();
  if (properties.length === 0) return null;

  return (
    <section className="flex flex-col gap-3 rounded-xl border border-edge-base bg-surface-subtle p-4">
      <h3 className="text-sm font-semibold text-ink-secondary">{t('issue.properties')}</h3>
      <div className="flex flex-col gap-2.5">
        {properties.map((prop) => (
          <PropertyField key={prop.id} prop={prop} value={values[prop.id]} onChange={(v) => onChange(prop.id, v)} />
        ))}
      </div>
    </section>
  );
}
PropertyField function · typescript · L33-L136 (104 LOC)
frontend/src/components/PropertyEditor.tsx
function PropertyField({ prop, value, onChange }: { prop: BoardProperty; value: unknown; onChange: (v: unknown) => void }) {
  const { t } = useTranslation();
  const Icon = typeIcons[prop.type];

  const [localText, setLocalText] = useState((value as string) ?? '');
  const [localNumber, setLocalNumber] = useState((value as string) ?? '');

  useEffect(() => {
    setLocalText((value as string) ?? '');
  }, [value]);

  useEffect(() => {
    setLocalNumber((value as string) ?? '');
  }, [value]);

  useEffect(() => {
    const timer = setTimeout(() => {
      if (localText !== (value ?? '')) {
        onChange(localText || null);
      }
    }, 300);
    return () => clearTimeout(timer);
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [localText]);

  useEffect(() => {
    const timer = setTimeout(() => {
      const numVal = localNumber !== '' ? Number(localNumber) : null;
      const prevVal = value !== undefined && value !== null ? String(value) : '';
      if (local
‹ prevpage 4 / 6next ›