Compare commits

...

9 Commits

Author SHA1 Message Date
Mathias Fredriksson 453a8ac15d style(docs): remove emoji to match original doc style
Remove 🚨 emoji from "Critical Pattern Migrations" section header to
maintain consistency with the original document style which uses no
emojis, only text emphasis and formatting.

All other style elements match the original:
- Section headers with ## and ###
- Bold emphasis for important terms
- Simple bullet lists with -
- Direct, imperative tone
- "(MUST FOLLOW)" matches original "(USE FIRST)" pattern
2025-11-27 10:03:09 +00:00
Mathias Fredriksson 9aac7ecfc5 refactor(docs): remove duplicate content from site/CLAUDE.md
Remove redundant sections and repeated information:

- Remove entire "Migration" section (lines 192-207) that duplicated
  Components, Styling, and Tailwind sections
- Remove duplicate "leave campsite better" mention (already in line 13)
- Remove duplicate "Never import from @emotion/react" (already stated)

Reduces documentation from 250 to 232 lines while maintaining all
unique information. Each concept is now documented once in its most
appropriate section.
2025-11-27 09:57:51 +00:00
Mathias Fredriksson 6e08d9c5a2 refactor(docs): reduce wordiness in pattern documentation
Condense verbose sections to be more scannable and direct:
- Icon section: Single-line import, inline replacement mappings
- Radix section: Consolidated prop changes into one sentence
- Emotion section: Reduced from 5 to 4 bullets, combined redundant points
- Chromatic section: Reduced from 5 to 3 bullets
- localStorage section: Reduced from 8 to 4 lines

Overall ~30% reduction in text while maintaining clarity. AI agents
scan for patterns and use code examples - keeping prose minimal and
directive improves efficiency.
2025-11-27 09:50:32 +00:00
Mathias Fredriksson d75fefba27 refactor(docs): remove deprecated pattern examples to prevent AI misuse
Remove code examples showing deprecated patterns (OLD/DO NOT USE blocks)
that could mislead AI agents into copying the wrong patterns.

Changes:
- Icon section: Only show lucide-react imports, not MUI icons
- Radix section: Only show correct Radix pattern, not MUI pattern
- Emotion section: Only show Tailwind classes, not emotion imports
- Testing section: Only show queries parameter, not spyOn pattern

This prevents AI agents from pattern-matching and copying deprecated
code while maintaining clear guidance on what to replace and how.
2025-11-27 09:48:04 +00:00
Mathias Fredriksson a72a0cd29c fix(docs): add blank lines around lists for markdown linter
Fix markdown linting errors MD032 and MD031:
- Add blank lines around lists (MD032)
- Add blank lines around fenced code blocks (MD031)

Ensures site/CLAUDE.md passes markdownlint-cli2 checks.
2025-11-26 13:49:02 +00:00
Mathias Fredriksson 1f0280e6fb docs(site): document critical frontend pattern migrations
Add comprehensive documentation for active frontend pattern migrations
discovered through analysis of 200+ git commits touching site/ files:

Core Migrations:
- Emotion → Tailwind CSS (strict 'no new emotion' policy)
- MUI Components → Custom/Radix/shadcn (Tooltips, Tables, Buttons)
- MUI Icons → lucide-react with specific icon mappings
- spyOn → queries parameter for GET endpoint mocks in Storybook
- localStorage → user_configs table for user preferences

New Documentation:
- Icon migration mappings (BusinessIcon→Building2Icon, etc.)
- Radix component prop naming conventions (placement→side)
- cn() utility usage for conditional className merging
- Chromatic testing best practices (prefer snapshots over assertions)

Includes concrete before/after examples and migration patterns to guide
developers away from deprecated approaches toward current best practices.

Analysis based on PRs: #20948, #20946, #20938, #20905, #20900, #20869,
#20849, #20808, #20530, #20479, #20261, #20201, #20200, #20193, #20318

---

🤖 This change was written by Claude Sonnet 4.5 Thinking using mux and reviewed by a human 🏂
2025-11-26 13:43:55 +00:00
Mathias Fredriksson b7d8918d60 fix(site): only show active tasks in waiting for input tab (#20933)
This change filters out non-active tasks from the "Waiting for input"
tab filter for the tasks list.

---

🤖 This change was initially written by Claude Code using Coder Tasks, then reviewed and edited by a human 🏂
2025-11-26 13:13:39 +00:00
Danielle Maywood e7dbbcde87 fix: do not notify marked for deletion for deleted workspaces (#20937)
Closes https://github.com/coder/coder/issues/20913

I've ran the test without the fix, verified the test caught the issue,
then applied the fix, and confirmed the issue no longer happens.

---

🤖 PR was initially written by Claude Opus 4.5 Thinking using Claude Code
and then review by a human 👩
2025-11-26 09:23:16 +00:00
Zach bbf7b137da fix(cli): remove defaulting to keyring when --global-config set (#20943)
This fixes a regression that caused the VS code extension to be unable
to authenticate after making keyring usage on by default. This is
because the VS code extension assumes the CLI will always use the
session token stored on disk, specifically in the directory specified by
--global-config.

This fix makes keyring usage enabled when the --global-config directory
is not set. This is a bit wonky but necessary to allow the extension to
continue working without modification and without backwards compat
concerns. In the future we should modify these extensions to either
access the credential in the keyring (like Coder Desktop) or some other
approach that doesn't rely on the session token being stored on disk.

Tests:
`coder login dev.coder.com` -> token stored in keyring
`coder login --global-config=/tmp/ dev.coder.com` -> token stored in
`/tmp/session`
2025-11-26 10:17:31 +01:00
12 changed files with 305 additions and 44 deletions
+4 -4
View File
@@ -61,10 +61,10 @@ func NewWithCommand(
t testing.TB, cmd *serpent.Command, args ...string,
) (*serpent.Invocation, config.Root) {
configDir := config.Root(t.TempDir())
// Keyring usage is disabled here because many existing tests expect the session token
// to be stored on disk and is not properly instrumented for parallel testing against
// the actual operating system keyring.
invArgs := append([]string{"--global-config", string(configDir), "--use-keyring=false"}, args...)
// Keyring usage is disabled here when --global-config is set because many existing
// tests expect the session token to be stored on disk and is not properly instrumented
// for parallel testing against the actual operating system keyring.
invArgs := append([]string{"--global-config", string(configDir)}, args...)
return setupInvocation(t, cmd, invArgs...), configDir
}
+2
View File
@@ -54,6 +54,7 @@ func setupKeyringTestEnv(t *testing.T, clientURL string, args ...string) keyring
serviceName := keyringTestServiceName(t)
root.WithKeyringServiceName(serviceName)
root.UseKeyringWithGlobalConfig()
inv, cfg := clitest.NewWithDefaultKeyringCommand(t, cmd, args...)
@@ -169,6 +170,7 @@ func TestUseKeyring(t *testing.T) {
logoutCmd, err := logoutRoot.Command(logoutRoot.AGPL())
require.NoError(t, err)
logoutRoot.WithKeyringServiceName(env.serviceName)
logoutRoot.UseKeyringWithGlobalConfig()
logoutInv, _ := clitest.NewWithDefaultKeyringCommand(t, logoutCmd,
"logout",
+23 -9
View File
@@ -483,9 +483,9 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err
Flag: varUseKeyring,
Env: envUseKeyring,
Description: "Store and retrieve session tokens using the operating system " +
"keyring. Enabled by default. If the keyring is not supported on the " +
"current platform, file-based storage is used automatically. Set to " +
"false to force file-based storage.",
"keyring. This flag is ignored and file-based storage is used when " +
"--global-config is set or keyring usage is not supported on the current " +
"platform. Set to false to force file-based storage on supported platforms.",
Default: "true",
Value: serpent.BoolOf(&r.useKeyring),
Group: globalGroup,
@@ -536,11 +536,12 @@ type RootCmd struct {
disableDirect bool
debugHTTP bool
disableNetworkTelemetry bool
noVersionCheck bool
noFeatureWarning bool
useKeyring bool
keyringServiceName string
disableNetworkTelemetry bool
noVersionCheck bool
noFeatureWarning bool
useKeyring bool
keyringServiceName string
useKeyringWithGlobalConfig bool
}
// InitClient creates and configures a new client with authentication, telemetry,
@@ -721,8 +722,14 @@ func (r *RootCmd) createUnauthenticatedClient(ctx context.Context, serverURL *ur
// flag.
func (r *RootCmd) ensureTokenBackend() sessionstore.Backend {
if r.tokenBackend == nil {
// Checking for the --global-config directory being set is a bit wonky but necessary
// to allow extensions that invoke the CLI with this flag (e.g. VS code) to continue
// working without modification. In the future we should modify these extensions to
// either access the credential in the keyring (like Coder Desktop) or some other
// approach that doesn't rely on the session token being stored on disk.
assumeExtensionInUse := r.globalConfig != config.DefaultDir() && !r.useKeyringWithGlobalConfig
keyringSupported := runtime.GOOS == "windows" || runtime.GOOS == "darwin"
if r.useKeyring && keyringSupported {
if r.useKeyring && !assumeExtensionInUse && keyringSupported {
serviceName := sessionstore.DefaultServiceName
if r.keyringServiceName != "" {
serviceName = r.keyringServiceName
@@ -742,6 +749,13 @@ func (r *RootCmd) WithKeyringServiceName(serviceName string) {
r.keyringServiceName = serviceName
}
// UseKeyringWithGlobalConfig enables the use of the keyring storage backend
// when the --global-config directory is set. This is only intended as an override
// for tests, which require specifying the global config directory for test isolation.
func (r *RootCmd) UseKeyringWithGlobalConfig() {
r.useKeyringWithGlobalConfig = true
}
type AgentAuth struct {
// Agent Client config
agentToken string
+4 -3
View File
@@ -111,9 +111,10 @@ variables or flags.
--use-keyring bool, $CODER_USE_KEYRING (default: true)
Store and retrieve session tokens using the operating system keyring.
Enabled by default. If the keyring is not supported on the current
platform, file-based storage is used automatically. Set to false to
force file-based storage.
This flag is ignored and file-based storage is used when
--global-config is set or keyring usage is not supported on the
current platform. Set to false to force file-based storage on
supported platforms.
-v, --verbose bool, $CODER_VERBOSE
Enable verbose output.
+1
View File
@@ -23751,6 +23751,7 @@ SET
WHERE
template_id = $3
AND dormant_at IS NOT NULL
AND deleted = false
-- Prebuilt workspaces (identified by having the prebuilds system user as owner_id)
-- should not have their dormant or deleting at set, as these are handled by the
-- prebuilds reconciliation loop.
+1
View File
@@ -846,6 +846,7 @@ SET
WHERE
template_id = @template_id
AND dormant_at IS NOT NULL
AND deleted = false
-- Prebuilt workspaces (identified by having the prebuilds system user as owner_id)
-- should not have their dormant or deleting at set, as these are handled by the
-- prebuilds reconciliation loop.
+1 -1
View File
@@ -179,7 +179,7 @@ Disable network telemetry. Network telemetry is collected when connecting to wor
| Environment | <code>$CODER_USE_KEYRING</code> |
| Default | <code>true</code> |
Store and retrieve session tokens using the operating system keyring. Enabled by default. If the keyring is not supported on the current platform, file-based storage is used automatically. Set to false to force file-based storage.
Store and retrieve session tokens using the operating system keyring. This flag is ignored and file-based storage is used when --global-config is set or keyring usage is not supported on the current platform. Set to false to force file-based storage on supported platforms.
### --global-config
+4 -3
View File
@@ -70,9 +70,10 @@ variables or flags.
--use-keyring bool, $CODER_USE_KEYRING (default: true)
Store and retrieve session tokens using the operating system keyring.
Enabled by default. If the keyring is not supported on the current
platform, file-based storage is used automatically. Set to false to
force file-based storage.
This flag is ignored and file-based storage is used when
--global-config is set or keyring usage is not supported on the
current platform. Set to false to force file-based storage on
supported platforms.
-v, --verbose bool, $CODER_VERBOSE
Enable verbose output.
@@ -737,6 +737,105 @@ func TestNotifications(t *testing.T) {
require.Contains(t, sent[i].Targets, dormantWs.OwnerID)
}
})
// Regression test for https://github.com/coder/coder/issues/20913
// Deleted workspaces should not receive dormancy notifications.
t.Run("DeletedWorkspacesNotNotified", func(t *testing.T) {
t.Parallel()
var (
db, _ = dbtestutil.NewDB(t)
ctx = testutil.Context(t, testutil.WaitLong)
user = dbgen.User(t, db, database.User{})
file = dbgen.File(t, db, database.File{
CreatedBy: user.ID,
})
templateJob = dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
FileID: file.ID,
InitiatorID: user.ID,
Tags: database.StringMap{
"foo": "bar",
},
})
timeTilDormant = time.Minute * 2
templateVersion = dbgen.TemplateVersion(t, db, database.TemplateVersion{
CreatedBy: user.ID,
JobID: templateJob.ID,
OrganizationID: templateJob.OrganizationID,
})
template = dbgen.Template(t, db, database.Template{
ActiveVersionID: templateVersion.ID,
CreatedBy: user.ID,
OrganizationID: templateJob.OrganizationID,
TimeTilDormant: int64(timeTilDormant),
TimeTilDormantAutoDelete: int64(timeTilDormant),
})
)
// Create a dormant workspace that is NOT deleted.
activeDormantWorkspace := dbgen.Workspace(t, db, database.WorkspaceTable{
OwnerID: user.ID,
TemplateID: template.ID,
OrganizationID: templateJob.OrganizationID,
LastUsedAt: time.Now().Add(-time.Hour),
})
_, err := db.UpdateWorkspaceDormantDeletingAt(ctx, database.UpdateWorkspaceDormantDeletingAtParams{
ID: activeDormantWorkspace.ID,
DormantAt: sql.NullTime{
Time: activeDormantWorkspace.LastUsedAt.Add(timeTilDormant),
Valid: true,
},
})
require.NoError(t, err)
// Create a dormant workspace that IS deleted.
deletedDormantWorkspace := dbgen.Workspace(t, db, database.WorkspaceTable{
OwnerID: user.ID,
TemplateID: template.ID,
OrganizationID: templateJob.OrganizationID,
LastUsedAt: time.Now().Add(-time.Hour),
Deleted: true, // Mark as deleted
})
_, err = db.UpdateWorkspaceDormantDeletingAt(ctx, database.UpdateWorkspaceDormantDeletingAtParams{
ID: deletedDormantWorkspace.ID,
DormantAt: sql.NullTime{
Time: deletedDormantWorkspace.LastUsedAt.Add(timeTilDormant),
Valid: true,
},
})
require.NoError(t, err)
// Setup dependencies
notifyEnq := notificationstest.NewFakeEnqueuer()
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
const userQuietHoursSchedule = "CRON_TZ=UTC 0 0 * * *" // midnight UTC
userQuietHoursStore, err := schedule.NewEnterpriseUserQuietHoursScheduleStore(userQuietHoursSchedule, true)
require.NoError(t, err)
userQuietHoursStorePtr := &atomic.Pointer[agplschedule.UserQuietHoursScheduleStore]{}
userQuietHoursStorePtr.Store(&userQuietHoursStore)
templateScheduleStore := schedule.NewEnterpriseTemplateScheduleStore(userQuietHoursStorePtr, notifyEnq, logger, nil)
// Lower the dormancy TTL to ensure the schedule recalculates deadlines and
// triggers notifications.
_, err = templateScheduleStore.Set(dbauthz.AsNotifier(ctx), db, template, agplschedule.TemplateScheduleOptions{
TimeTilDormant: timeTilDormant / 2,
TimeTilDormantAutoDelete: timeTilDormant / 2,
})
require.NoError(t, err)
// We should only receive a notification for the non-deleted dormant workspace.
sent := notifyEnq.Sent()
require.Len(t, sent, 1, "expected exactly 1 notification for the non-deleted workspace")
require.Equal(t, sent[0].UserID, activeDormantWorkspace.OwnerID)
require.Equal(t, sent[0].TemplateID, notifications.TemplateWorkspaceMarkedForDeletion)
require.Contains(t, sent[0].Targets, activeDormantWorkspace.ID)
// Ensure the deleted workspace was NOT notified
for _, notification := range sent {
require.NotContains(t, notification.Targets, deletedDormantWorkspace.ID,
"deleted workspace should not receive notifications")
}
})
}
func TestTemplateTTL(t *testing.T) {
+124 -22
View File
@@ -1,5 +1,17 @@
# Frontend Development Guidelines
## Critical Pattern Migrations (MUST FOLLOW)
The following patterns are actively being migrated and have **STRICT policies**:
1. **Emotion → Tailwind**: "No new emotion styles, full stop" - Always use Tailwind CSS
2. **MUI Components → Custom/Radix Components**: Replace MUI components (Tooltips, Tables, Buttons) with custom/shadcn equivalents
3. **MUI Icons → lucide-react**: All icons must use lucide-react, never MUI icons
4. **spyOn → queries parameter**: Use `queries` in story parameters for GET endpoint mocks
5. **localStorage → user_configs**: Store user preferences in backend, not browser storage
When touching existing code, **"leave the campsite better than you found it"** - refactor old patterns to new ones even if not directly related to your changes.
## TypeScript LSP Navigation (USE FIRST)
When investigating or editing TypeScript/React code, always use the TypeScript language server tools for accurate navigation:
@@ -26,25 +38,84 @@ When investigating or editing TypeScript/React code, always use the TypeScript l
## Components
- MUI components are deprecated - migrate away from these when encountered
- Use shadcn/ui components first - check `site/src/components` for existing implementations.
- **MUI components are deprecated** - migrate away from these when encountered
- Replace `@mui/material/Tooltip` with custom `Tooltip` component (Radix-based)
- Default 100ms delay via global tooltip provider
- Use `delayDuration={0}` when immediate tooltip needed
- Replace MUI Tables with custom table components
- Replace MUI Buttons with shadcn Button components
- Systematically replace MUI components with custom/shadcn equivalents
- Use shadcn/ui components first - check `site/src/components` for existing implementations
- Do not use shadcn CLI - manually add components to maintain consistency
- The modules folder should contain components with business logic specific to the codebase.
- The modules folder should contain components with business logic specific to the codebase
- Create custom components only when shadcn alternatives don't exist
### Icon Migration: MUI Icons → lucide-react
Never import from `@mui/icons-material`. Use `lucide-react` instead.
```tsx
import { Building2Icon, UsersIcon, GlobeIcon, UserIcon } from "lucide-react";
```
**Common replacements:** `BusinessIcon``Building2Icon`, `GroupIcon``UsersIcon`, `PublicIcon``GlobeIcon`, `PersonIcon``UserIcon`
### MUI → Radix Component Prop Naming
When migrating from MUI to Radix components, use Radix naming conventions:
```tsx
<Tooltip side="top">
<TooltipTrigger>Hover me</TooltipTrigger>
<TooltipContent>Tooltip text</TooltipContent>
</Tooltip>
```
**Prop changes from MUI:** Use `side` instead of `placement`, remove `PopperProps`, and use `TooltipContent` children instead of `title` prop.
## Styling
- Emotion CSS is deprecated. Use Tailwind CSS instead.
- Use custom Tailwind classes in tailwind.config.js.
- **Emotion CSS is STRICTLY DEPRECATED: "no new emotion styles, full stop"**
- Never use `@emotion/react`, `css` prop, `useTheme()`, or emotion styled components
- Always use Tailwind CSS utility classes instead
- Use custom Tailwind classes in tailwind.config.js
- Tailwind CSS reset is currently not used to maintain compatibility with MUI
- Responsive design - use Tailwind's responsive prefixes (sm:, md:, lg:, xl:)
- Do not use `dark:` prefix for dark mode
### Common Emotion → Tailwind Migrations
Use Tailwind CSS utility classes:
```tsx
<div className="flex flex-col gap-2">
<div className="flex items-center gap-6">
<span className="font-medium text-content-primary">
Content here
</span>
</div>
</div>
```
**Common replacements:**
- Replace `css={visuallyHidden}` with `className="sr-only"`
- Replace `Stack` component with Tailwind flex (`flex`, `flex-col`, `gap-*`)
- Replace emotion `css` prop and theme colors with Tailwind utilities (`text-content-primary`, `bg-surface-secondary`, `border-border-default`)
- Use lucide-react icons with `size-icon-sm`, `size-icon-xs` classes
## Tailwind Best Practices
- Group related classes
- Use semantic color names from the theme inside `tailwind.config.js` including `content`, `surface`, `border`, `highlight` semantic tokens
- Prefer Tailwind utilities over custom CSS when possible
- For conditional classes, use the `cn()` utility (from `utils/cn`) which combines `clsx` and `tailwind-merge`
```tsx
import { cn } from "utils/cn";
<div className={cn("base-classes", condition && "conditional-classes", className)} />
```
## General Code style
@@ -53,6 +124,54 @@ When investigating or editing TypeScript/React code, always use the TypeScript l
- Prefer `for...of` over `forEach` for iteration
- **Biome** handles both linting and formatting (not ESLint/Prettier)
## Testing Patterns
### Storybook: queries parameter for GET endpoint mocks
**PREFERRED PATTERN**: For GET endpoint mocks in Storybook stories, use `queries` parameter instead of `spyOn`.
```tsx
// Use queries parameter pattern
parameters: {
queries: [
{
key: usersKey({ q: "" }),
data: {
users: MockUsers,
count: MockUsers.length,
},
},
{
key: getTemplatesQueryKey({ q: "has-ai-task:true" }),
data: [MockTemplate],
},
],
}
```
**Important notes:**
- This applies specifically to GET endpoint mocks in Storybook stories
- `spyOn` is still appropriate for other mock types (POST, PUT, DELETE, non-GET endpoints)
- Must import the correct query key functions (e.g., `usersKey`, `getTemplatesQueryKey`)
### Chromatic/Storybook Testing Best Practices
- Prefer visual validation through Chromatic snapshots over programmatic assertions
- Remove redundant assertions that duplicate snapshot validation
- Stories are snapshot tests - rely on screenshots to verify correctness
## State Storage
### localStorage vs user_configs table
**IMPORTANT**: Use `user_configs` table for user preferences, NOT `localStorage`.
- localStorage is browser-specific; user preferences should persist across devices
- Follow `theme_preference` as reference implementation
- Use localStorage only for truly transient UI state
- **Key principle**: User preferences should be tied to their account, not their browser
## Workflow
- Be sure to typecheck when you're done making a series of code changes
@@ -69,23 +188,6 @@ When investigating or editing TypeScript/React code, always use the TypeScript l
4. `pnpm test` - Run affected unit tests
5. Visual check in Storybook if component changes
## Migration (MUI → shadcn) (Emotion → Tailwind)
### Migration Strategy
- Identify MUI components in current feature
- Find shadcn equivalent in existing components
- Create wrapper if needed for missing functionality
- Update tests to reflect new component structure
- Remove MUI imports once migration complete
### Migration Guidelines
- Use Tailwind classes for all new styling
- Replace Emotion `css` prop with Tailwind classes
- Leverage custom color tokens: `content-primary`, `surface-secondary`, etc.
- Use `className` with `clsx` for conditional styling
## React Rules
### 1. Purity & Immutability
+41 -1
View File
@@ -10,7 +10,7 @@ import { withAuthProvider, withProxyProvider } from "testHelpers/storybook";
import type { Meta, StoryObj } from "@storybook/react-vite";
import { API } from "api/api";
import { MockUsers } from "pages/UsersPage/storybookData/users";
import { expect, spyOn, userEvent, within } from "storybook/test";
import { expect, spyOn, userEvent, waitFor, within } from "storybook/test";
import { getTemplatesQueryKey } from "../../api/queries/templates";
import TasksPage from "./TasksPage";
@@ -145,6 +145,29 @@ export const LoadedTasksWaitingForInputTab: Story = {
spyOn(API, "getTasks").mockResolvedValue([
{
...firstTask,
id: "active-idle-task",
display_name: "Active Idle Task",
status: "active",
current_state: {
...firstTask.current_state,
state: "idle",
},
},
{
...firstTask,
id: "paused-idle-task",
display_name: "Paused Idle Task",
status: "paused",
current_state: {
...firstTask.current_state,
state: "idle",
},
},
{
...firstTask,
id: "error-idle-task",
display_name: "Error Idle Task",
status: "error",
current_state: {
...firstTask.current_state,
state: "idle",
@@ -161,6 +184,23 @@ export const LoadedTasksWaitingForInputTab: Story = {
name: /waiting for input/i,
});
await userEvent.click(waitingForInputTab);
// Wait for the table to update after tab switch
await waitFor(async () => {
const table = canvas.getByRole("table");
const tableContent = within(table);
// Active idle task should be visible
expect(tableContent.getByText("Active Idle Task")).toBeInTheDocument();
// Only active idle tasks should be visible in the table
expect(
tableContent.queryByText("Paused Idle Task"),
).not.toBeInTheDocument();
expect(
tableContent.queryByText("Error Idle Task"),
).not.toBeInTheDocument();
});
});
},
};
+1 -1
View File
@@ -44,7 +44,7 @@ const TasksPage: FC = () => {
refetchInterval: 10_000,
});
const idleTasks = tasksQuery.data?.filter(
(task) => task.current_state?.state === "idle",
(task) => task.status === "active" && task.current_state?.state === "idle",
);
const displayedTasks =
tab.value === "waiting-for-input" ? idleTasks : tasksQuery.data;