The delete translations lived under myNamespaces.delete.* but every caller
looks them up as namespace.delete.* (my-namespaces.tsx and
delete-namespace-dialog.tsx). i18next fell back to the raw key, so the
rendered button read 'namespace.delete.button' and the E2E regex
/^delete$/i never matched. Move the block under namespace.* in both
locales to make the keys resolvable.
The failed CI run showed three timeouts on getByRole('link'), but the
namespace cards render as clickable divs and the archive flow uses a
ConfirmDialog with no textarea. Rewrite the delete spec to operate
directly on the card, and seed the archived state through the real API
instead of walking the archive UI.
Add complete frontend implementation for namespace deletion:
- API layer: add delete() method to namespaceApi
- Hooks layer: add useDeleteNamespace() mutation hook
- UI layer: add DeleteNamespaceDialog component with reason input
- Add delete button in my-namespaces page (only shown when canDelete is true)
- Add i18n keys for delete confirmation, reason input, and success/error messages
- Update ManagedNamespace type to include canDelete permission flag
- Fix test mocks to include canDelete property
The delete dialog requires users to provide a deletion reason before confirming,
following the backend requirement. After successful deletion, users are redirected
to the my-namespaces page.
Implements namespace deletion via soft delete (DELETED status) to preserve audit history and prevent accidental data loss. Only namespace OWNER can delete, and namespace must be ARCHIVED first with no ACTIVE skills.
- Add DELETED status to NamespaceStatus enum
- Add canDelete() permission check in NamespaceAccessPolicy
- Implement deleteNamespace() in NamespaceGovernanceService with validation
- Add DELETE /api/web/namespaces/{slug} endpoint in NamespaceController
- Add i18n messages for delete errors (en/zh)
- Add V41 migration to document DELETED status
Resolves#341
* feat(publish): increase max file count from 100 to 500
Configurable via SKILLHUB_PUBLISH_MAX_FILE_COUNT env var.
* feat(publish): support SKILL.md in subdirectory with warning for ignored files
When SKILL.md is found in a single subdirectory (e.g. my-skill/SKILL.md),
promote that directory's contents to root and discard files outside it.
Discarded files are reported as warnings through the existing confirm flow.
* feat(publish): pass extraction warnings through confirm flow
When files are ignored during SKILL.md subdirectory promotion,
warnings are surfaced to the user via the existing precheck confirm dialog.
* fix(security): add invalidSessionStrategy to return 401 on expired session
Handles the case where Spring Security detects an invalid session cookie,
returning a clean 401 JSON response instead of triggering cascading exceptions.
Closes#360 (part 1/2)
* fix(security): handle session invalidation IllegalStateException as 401
Catches IllegalStateException with "Session was invalidated" message and
returns 401 instead of letting it fall through to the generic 500 handler.
Non-session IllegalStateExceptions are re-thrown to the catch-all handler.
Closes#360 (part 2/2)
* feat(publish): filter macOS metadata and add integration tests
Skip __MACOSX/, .DS_Store, and ._ resource fork entries during zip
extraction. Add integration tests for nested SKILL.md warning flow,
session invalidation 401 response, and macOS metadata filtering.
* test(publish): add real-world macOS zip and edge case integration tests
Covers: macOS zip with nested SKILL.md + __MACOSX + .DS_Store + stray files,
simple macOS single-folder case, and missing SKILL.md fallback behavior.
- Add visual separator between StarButton and SubscribeButton
- Use optimistic updates in useToggleSubscription for instant feedback
- Remove isLoading guard that caused button to unmount during refetch
When access-key / secret-key are left blank, fall back to the AWS
DefaultCredentialsProvider chain so that deployments on EC2, ECS,
and EKS can authenticate via instance profile, task role, or IRSA
without static credentials.
- Extract buildCredentialsProvider() in S3StorageService
- Add sts dependency for Web Identity Token (EKS) support
- Add unit tests for credential provider selection
- Update storage-spi docs (zh + en) and env example
Include subscriptionCount in SkillDetailDTO and SkillDetailResponse
so the frontend SubscribeButton receives the updated count after
subscribe/unsubscribe mutations.
Revert emailVerified check in EmailDomainAccessPolicy to preserve
backward compatibility with GitHub/GitLab OAuth users. Instead, null
unverified emails in CustomOidcUserService.toOAuthClaims() so
EmailDomainAccessPolicy naturally denies them via null email.
Add SLF4J logging to CustomOidcUserService for OIDC authentication
flow tracing and failure diagnostics.
Add registration ID collision warning to deployment docs.
Published skills start in PENDING_REVIEW status with canInteract=false,
so the SubscribeButton is not rendered. Use admin user to approve the
review first, following the same pattern as search-seed and review-seed.
Use .first() for h1 heading locator since skill detail page
renders two h1 elements (page title + README heading).
Add page-ready wait in second test before locating Subscribe button.
- Add null/blank validation for OIDC sub claim in CustomOidcUserService
- Throw OAuth2AuthenticationException when sub is missing or blank
- Complete .env.release.example with all required OIDC environment variables
- Add test cases for sub validation and providerLogin fallback scenarios
- All 5 tests passing