Imported from golosoman/YouTube-Shorts-Limiter (
AGENTS.md). Install upstream withnpx skills add golosoman/YouTube-Shorts-Limiter. Copyright stays with the author.
AGENTS.md
Project Purpose
- This browser extension limits YouTube Shorts watch time and general YouTube watch time.
- Ordinary YouTube is available until the general YouTube budget reaches its limit.
- Shorts time counts toward both the Shorts budget and the general YouTube budget.
- Allowed durations and cooldown durations are changed through the popup.
- Usage state and settings are stored in
chrome.storage.local. - Manifest V3 background service workers are not persistent processes.
Tech Stack
- TypeScript
- WXT
- Chrome Manifest V3
- React
- Radix UI primitives
- Vitest
- ESLint flat config
- Prettier
- zod for env validation
- pnpm
Architecture
domain/
- Contains entities and value objects.
- Never imports app, infrastructure, composition, entrypoints, config, or envs.
app/interfaces/
- Contains use-case, service, browser, storage, clock, and logger contracts.
- Keep
dto.tsanderror.tsbeside the contract that owns them.
app/use-cases/
- Implements use-case interfaces.
- Orchestrates only.
- Does not use Chrome API.
app/services/
- Implements service interfaces.
- Contains reusable application logic.
- Does not use Chrome API.
infrastructure/
- Implements app interfaces through
chrome.*and external APIs. - Direct
chrome.storage,chrome.tabs, and related APIs are allowed here.
composition/
- Wires dependencies in
createAppContainer.ts.
presentation/
- Contains React UI components, view-model mappers, formatting helpers, and CSS.
- May import app DTO types and small domain value objects for display formatting.
- May import public config from
@/configfor UI constraints and route values. - Never imports infrastructure, composition, entrypoints, or Chrome APIs.
- Contains no business rules for limiting, cooldowns, or URL classification.
entrypoints/
- WXT entrypoints.
- Background entrypoint contains only event wiring and use-case calls.
- Popup and blocked page entrypoints only mount React apps and pass use-case callbacks.
- No business logic.
envs/
- The only place that reads
import.meta.env.
config/
- Typed project configuration.
- App and infrastructure import config only with
import { config } from "@/config".
Folder Naming Rules
Inside contextual folders, files use short names:
interface.tsdto.tserror.tsservice.tsusecase.tsrepository.tsmapper.ts
Shared, domain, and top-level files use full names:
AppError.tsUsageState.tsWatchPolicy.tsDurationMs.tsTimestampMs.tsShortsUrl.tsWatchScope.tscreateAppContainer.tshandleError.ts
Presentation component files use descriptive names such as:
PopupApp.tsxBlockedApp.tsxScopeCard.tsxSettingsForm.tsxStatusChip.tsx
Dependency Rules
domainnever importsapp,infrastructure,composition,entrypoints,config, orenvs.appnever importsinfrastructure.infrastructureimplementsapp/interfaces.presentationnever importsinfrastructure,composition, orentrypoints.presentationdoes not accesschrome.*.- Entrypoints do not contain business logic.
import.meta.envis allowed only insrc/envs.chrome.*is allowed only ininfrastructureandentrypoints.- App and infrastructure import config only from
@/config. - User-editable settings are not env values.
No Magic Values Policy
This project does not ban all numeric literals.
The rule is:
- Business values must be named.
- Repeated values must be named.
- Unit-sensitive values must go through value objects/helpers.
- Infrastructure identifiers must be constants or config.
- Obvious local literals may remain inline.
Do not create meaningless constants like:
const ONE = 1;
const TWO = 2;
Do create meaningful constants/config values like:
initial.shorts.allowedDurationinitial.shorts.cooldownDurationinitial.youtube.allowedDurationinitial.youtube.cooldownDurationallowedDuration.minallowedDuration.maxalarmPeriodblockedPagePath- usage-state storage key
- settings storage key
- block reasons
- event source names
Config And Env Rules
envs/readsimport.meta.env.config/composes typed application config.- User settings are persisted through
SettingsRepository. - Do not put secrets into env because extension bundles are inspectable.
- Do not create
defaults.ts. - Do not create
config/env.ts. - Use
config/index.tsas the single public config import.
DTO Naming Rules
- External boundary DTOs use explicit direction suffixes:
SomethingInputDtoSomethingOutputDto
- Use-case input and output types follow this rule.
- Browser adapter output types follow this rule, for example
ActiveTabOutputDto. - Persisted storage schema DTOs should either use explicit output naming or a clearly schema-oriented name.
- React UI types are not DTOs; use
ViewModelsuffix for presentation data.
Use-case Style
- A use-case has one responsibility.
- A use-case depends on interfaces, not concrete infrastructure.
- A use-case can use app services.
- A use-case catches errors only to wrap them into its own
AppErrorwhen appropriate. - A use-case must be testable without Chrome API.
Service Style
- A service implements
app/interfaces/services/.../interface.ts. - A service contains reusable pure logic where possible.
- No direct Chrome API.
- No storage access unless it is explicitly an infrastructure service or repository.
Repository / Infrastructure Style
- Repositories wrap
chrome.storage.local. - Persisted data is unknown and must be mapped/validated.
- Corrupted storage data falls back safely.
- Storage keys come from
config.storage. - Low-level errors are wrapped into app-level custom errors.
Error Handling
- All custom errors extend
AppError. - Error codes are typed constants.
- Catch variables are
unknown. - Infrastructure wraps low-level errors.
- Entrypoints use
handleError. - Do not swallow errors silently.
Popup / UI Style
- Popup is a presentation boundary built with React.
- Entrypoint creates the app container and passes use-case callbacks into React.
- React components receive props/view-models and contain no business rules.
- DOM access belongs in the mount shell only when locating the React root element.
- Settings are changed through
UpdateSettingsUseCase. - Status is read through
GetStatusUseCase. - Presentation can show scope cards for
shortsandyoutube, but limit decisions stay in app/domain services. - Use Radix primitives selectively when they provide accessibility or interaction behavior.
- Do not add Tailwind, shadcn, or a styled component kit unless the UI has grown enough to justify the coupling.
- Keep visual styling in CSS with shared tokens under
src/presentation/shared.
Testing Rules
- Business logic must have unit tests.
- Tests should avoid Chrome API unless mocking infrastructure.
- App services and app use-cases are primary test targets.
- Mappers must test corrupted persisted data.
- Mappers must test storage migrations when persisted DTO shape changes.
- Presentation components should have jsdom tests when their behavior or conditional rendering changes.
- Every new use-case needs tests.
- Test literals are allowed when they are meaningful test data.
Required Commands
pnpm install
pnpm dev
pnpm typecheck
pnpm lint
pnpm test
pnpm build
pnpm quality
pnpm format:check
Before Submitting Changes
- Typecheck passes.
- Lint passes.
- Tests pass.
- Build passes.
- No business magic values added.
- No layer dependency violations.
- No raw
chrome.*inappordomain. - No raw
chrome.*inpresentation. - No raw
import.meta.envoutsideenvs. - Boundary DTOs use
InputDtoorOutputDtosuffixes. - README updated if behavior changed.
- AGENTS.md updated if project style changed.
Adding New Code
Add a new use-case
- Create
app/interfaces/use-cases/<name>/interface.ts. - Add
dto.tsanderror.tsonly if needed. - Implement
app/use-cases/<name>/usecase.ts. - Wire in
composition/createAppContainer.ts. - Add tests.
Add a new app service
- Create
app/interfaces/services/<name>/interface.ts. - Implement
app/services/<name>/service.ts. - Add tests.
Add a new infrastructure adapter
- Create interface under
app/interfaces/<boundary>/<name>/. - Implement under
infrastructure/<boundary>/<name>/. - Map unknown external data through
mapper.tsif needed. - Wrap low-level errors.
Add a new config value
- Add it to the correct
config/*.config.tsfile. - Export through
config/index.ts. - Never import config internals directly unless there is a strong reason.
Add a new presentation component
- Put shared primitives or helpers under
src/presentation/shared. - Put popup-specific components under
src/presentation/popup. - Put blocked-page components under
src/presentation/blocked. - Pass data as props or view-models.
- Keep Chrome API calls and business rules out of React components.
- Add jsdom tests for behavior, conditional rendering, or form submission.
Strictness
When in doubt, prefer:
- stronger typing;
- smaller permissions;
- named constants for business values;
- tested business logic;
- explicit boundaries;
- boring readable code over clever code.