Skip to main content

Work Detail Components

Overview

The work detail components form the most complex view in the Ever Works dashboard. They display and manage all aspects of a single work: metadata, items, AI generation, scheduling, deployment, history, comparisons, plugins, members, and settings. The components live in apps/web/src/components/works/detail/ and are organized into sub-folders by tab. A React Context (WorkDetailContext) provides shared work state to all child components.

Architecture

WorkDetailContext (React Context Provider)
├── work: WorkDto
├── oauthConnection: OAuthConnectionDto | null
├── config: WorkConfigDto
├── repoLinks: { main, dataRepo, websiteRepo }
├── permissions: PermissionsMap

├── WorkHeader
│ ├── Work name + shared role badge
│ ├── Generation status badge (with step progress)
│ ├── Slug, owner, git provider link
│ ├── Creation date, website link
│ └── Action buttons

├── WorkTabs
│ ├── overview/ → Work overview stats and info
│ ├── items/ → Item listing, CRUD, filtering
│ ├── generator/ → AI generation configuration and execution
│ ├── schedule/ → Automated generation scheduling
│ ├── history/ → Generation history and logs
│ ├── comparisons/ → Item comparison management
│ ├── plugins/ → Work-level plugin configuration
│ ├── deploy/ → Website deployment settings
│ ├── members/ → Team member management (permission-gated)
│ └── settings/ → Work settings, prompts, website config

└── shared/ → Shared sub-components (status badges, selectors)

The work detail page can contain around 70 files across all sub-folders. This document covers the three foundational components: the context provider, header, and tab navigation.

Components

WorkDetailContext

File: apps/web/src/components/works/detail/WorkDetailContext.tsx

This component is a React Context provider that wraps the entire work detail page. It accepts the work's core data as props and makes it available to all descendant components via two hooks:

HookReturnsPurpose
useWorkDetail()Full context valueAccess work, config, OAuth, repo links
useWorkPermissions()PermissionsMapCheck specific permissions for the current user

Context value shape:

interface WorkDetailContextValue {
work: WorkDto;
oauthConnection: OAuthConnectionDto | null;
config: WorkConfigDto;
repoLinks: {
main: string | null;
dataRepo: string | null;
websiteRepo: string | null;
};
permissions: PermissionsMap;
}

Repo links are computed from the work's git provider and repository data. Each link is a fully qualified URL to the repository on GitHub, GitLab, or Bitbucket. The three repo types are:

  • main - The primary code/data repository.
  • dataRepo - The repository containing generated work data.
  • websiteRepo - The repository for the deployed website.

Permissions are resolved via a getPermissions utility that maps the current user's role (owner, admin, editor, viewer) to specific boolean flags.

import { WorkDetailProvider, useWorkDetail } from './WorkDetailContext';

// Provider wraps the entire detail page
<WorkDetailProvider work={workData} oauthConnection={oauthConn} config={dirConfig}>
<WorkHeader />
<WorkTabs />
</WorkDetailProvider>;

// Consumer hook in any child component
function SomeChildComponent() {
const { work, repoLinks } = useWorkDetail();
const permissions = useWorkPermissions();

if (!permissions.canEdit) return <p>Read-only access</p>;
return <p>{work.name}</p>;
}

WorkHeader

File: apps/web/src/components/works/detail/WorkHeader.tsx

PropTypeDescription
workIdstringThe work's unique ID

This component consumes useWorkDetail() and renders the work's header section with:

  1. Title row:

    • Work name as an h1.
    • Shared role badge (if the work is shared with the current user, shows their role: admin, editor, viewer).
    • Generation status badge showing the current generation state. If generation is in progress, it shows a step indicator (e.g., "Step 2/5: Generating items") with a progress animation.
  2. Metadata row:

    • Slug displayed as a code-formatted span.
    • Owner name.
    • Git provider link (opens the repository in a new tab) with the provider icon.
    • Creation date formatted with toLocaleDateString.
    • Website link (if deployed) opening in a new tab.
  3. Action buttons: Context-dependent actions like sync, edit, or generate, shown based on the user's permissions from useWorkPermissions().

<WorkHeader workId="dir_abc123" />

WorkTabs

File: apps/web/src/components/works/detail/WorkTabs.tsx

PropTypeDescription
workIdstringThe work's unique ID
activeTabstringCurrently active tab identifier

Renders a horizontal tab navigation bar. Each tab is a link that navigates to a different section of the work detail page. The available tabs are:

Tab IDLabelVisibility
overviewOverviewAlways
itemsItemsAlways
generatorWorkerAlways
scheduleScheduleAlways
historyHistoryAlways
comparisonsComparisonsAlways
pluginsPluginsAlways
deployDeployAlways
membersMembersPermission-gated (hidden for viewers)
settingsSettingsAlways

The active tab is visually indicated with a primary-colored bottom border and text. Inactive tabs use muted text with a hover effect. The tab bar scrolls horizontally on small screens.

Permission gating is handled by checking useWorkPermissions() - the members tab is only rendered if the user has the canManageMembers permission.

<WorkTabs workId="dir_abc123" activeTab="items" />

Implementation Details

Context Provider Pattern

The work detail context uses the standard React Context pattern with a provider component and consumer hooks. The context is created once at the page level (typically in a server component that fetches the data, then renders the client provider):

// Server component (page.tsx)
export default async function WorkPage({ params }) {
const work = await getWork(params.id);
const config = await getWorkConfig(params.id);
const oauth = await getOAuthConnection(params.id);

return (
<WorkDetailProvider work={work} oauthConnection={oauth} config={config}>
<WorkHeader workId={params.id} />
<WorkTabs workId={params.id} activeTab="overview" />
{/* Tab content */}
</WorkDetailProvider>
);
}

Generation Status Badge

The generation status badge in WorkHeader is a dynamic indicator that reflects the work's current generation state:

  • Idle - No badge or a muted "Ready" indicator.
  • Queued - Yellow badge with a clock icon.
  • In Progress - Animated blue badge showing the current step (e.g., "Analyzing repository", "Generating items", "Building markdown"). The step count is shown as "Step N/M".
  • Completed - Green badge with a check icon.
  • Failed - Red badge with an error icon.

The badge polls or receives real-time updates to reflect generation progress.

Repository links are computed based on the git provider type:

ProviderURL Pattern
GitHubhttps://github.com/{owner}/{repo}
GitLabhttps://gitlab.com/{owner}/{repo}
Bitbuckethttps://bitbucket.org/{owner}/{repo}

Each of the three repo types (main, data, website) can be null if the repository has not been created or linked yet.

Tab Routing

Tabs use Next.js App Router for navigation. Each tab corresponds to a nested route segment:

/dashboard/works/[id]/overview
/dashboard/works/[id]/items
/dashboard/works/[id]/generator
...

The activeTab prop is derived from the current URL segment and used to highlight the active tab.

Styling & Theming

The work detail components follow the standard design token patterns:

ElementClasses
Header backgroundbg-surface dark:bg-surface-dark
Tab barborder-b border-border dark:border-border-dark
Active tabtext-primary border-b-2 border-primary
Inactive tabtext-text-muted dark:text-text-muted-dark hover:text-text
Status badgesVariant-specific: bg-primary/10 text-primary, bg-success/10 text-success, bg-danger/10 text-danger
Role badgesbg-surface-secondary dark:bg-surface-secondary-dark text-text-muted

The generation status badge uses Tailwind's animate-pulse for the in-progress state.

Usage Examples

Full Work Detail Page Assembly

import { WorkDetailProvider } from '@/components/works/detail/WorkDetailContext';
import { WorkHeader } from '@/components/works/detail/WorkHeader';
import { WorkTabs } from '@/components/works/detail/WorkTabs';

export default async function WorkDetailPage({ params, searchParams }) {
const { id } = params;
const activeTab = searchParams.tab || 'overview';

const [work, config, oauth] = await Promise.all([getWork(id), getWorkConfig(id), getOAuthConnection(id)]);

return (
<WorkDetailProvider work={work} oauthConnection={oauth} config={config}>
<div className="space-y-6">
<WorkHeader workId={id} />
<WorkTabs workId={id} activeTab={activeTab} />
{/* Render active tab content */}
</div>
</WorkDetailProvider>
);
}

Consuming Context in a Tab Component

'use client';

import { useWorkDetail, useWorkPermissions } from '../WorkDetailContext';

export function OverviewTab() {
const { work, config, repoLinks } = useWorkDetail();
const permissions = useWorkPermissions();

return (
<div>
<h2>{work.name}</h2>
{repoLinks.main && (
<a href={repoLinks.main} target="_blank" rel="noopener">
View Repository
</a>
)}
{permissions.canEdit && <Button>Edit Work</Button>}
</div>
);
}