From 588fdc794bfc20014fa090e74d605266b0ad1a92 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Thu, 30 Jul 2026 15:52:50 +0200 Subject: [PATCH] docs: rewrite contributing guide, add QA and compatibility docs Issues are bug-only now. The feature-request template is gone, requests opened as issues get closed automatically, and feature talk moves to product discussions on GitHub Discussions and Discord. "Product discussion" is the term everywhere, recorded in the glossary. Contributing states the QA evidence bar and links out instead of teaching; docs/qa.md carries the depth. The protocol back-compat rules moved out of CLAUDE.md into docs/protocol-compatibility.md, since that constraint is invisible in development where both sides always run the same version. CLAUDE.md gains doc-writing and voice rules to keep docs from accumulating appended paragraphs. design.md gains an alignment section (glyph rails) and a layout-shift rule. --- .github/ISSUE_TEMPLATE/bug-report.yml | 2 +- .github/ISSUE_TEMPLATE/config.yml | 7 +- .github/ISSUE_TEMPLATE/feature-request.yml | 43 ------ CLAUDE.md | 52 ++++--- CONTRIBUTING.md | 156 ++++++++++++++++----- docs/design.md | 30 +++- docs/glossary.md | 1 + docs/protocol-compatibility.md | 59 ++++++++ docs/qa.md | 77 ++++++++++ 9 files changed, 323 insertions(+), 104 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/feature-request.yml create mode 100644 docs/protocol-compatibility.md create mode 100644 docs/qa.md diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 195651822..c793b23cf 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -6,7 +6,7 @@ body: - type: markdown attributes: value: | - I'm a solo maintainer and don't always keep up with GitHub Issues daily. If something is urgent or blocking you, [Discord](https://discord.gg/jz8T2uahpH) is the fastest place to reach me. + Issues are only for bugs. Product discussions go in [GitHub Discussions](https://github.com/getpaseo/paseo/discussions) or `#product` in [Discord](https://discord.gg/jz8T2uahpH) — feature requests opened as issues get closed automatically. Before opening, please: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index de533faa4..027b42a33 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,8 @@ blank_issues_enabled: false contact_links: - - name: Discord + - name: Product discussion on Discord url: https://discord.gg/jz8T2uahpH - about: Urgent or blocking issues, quick questions, sharing a video of a bug, or anything that's better as a chat. + about: Any discussion, product or otherwise. Product discussions go in #product — describe what you're trying to do, how you do it today, and where Paseo gets in the way. + - name: Product discussion on GitHub + url: https://github.com/getpaseo/paseo/discussions + about: Same conversation, on GitHub. Issues are only for bugs, and feature requests opened as issues get closed automatically. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml deleted file mode 100644 index cc08d17bc..000000000 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Feature request -description: Propose a new feature or a change to existing behavior. -title: "feat: " -labels: ["enhancement"] -body: - - type: markdown - attributes: - value: | - Paseo is opinionated and maintained by one person. Feature requests are welcome, but they get evaluated against product fit, not just usefulness, and the bar is whether the change keeps the product lean enough for one person to maintain. - - Big ideas are better discussed in [Discord](https://discord.gg/jz8T2uahpH) first. And please don't open a feature request and a PR at the same time, get alignment on the idea before writing code. - - - type: checkboxes - id: prior-search - attributes: - label: Prior search - options: - - label: I searched existing issues and discussions, and this isn't already proposed. - required: true - - - type: textarea - id: problem - attributes: - label: What's the problem - description: What are you actually trying to do, and why is the current behavior in the way? - placeholder: | - When I'm doing X, I want to Y, but Paseo currently Z. - validations: - required: true - - - type: textarea - id: proposal - attributes: - label: What would solve it - description: A rough sketch of the change. Mockups, screenshots from other apps, or a short video are very welcome, especially for UI proposals. - validations: - required: true - - - type: textarea - id: alternatives - attributes: - label: Alternatives you considered - description: Optional. What else did you try, and why doesn't it work? diff --git a/CLAUDE.md b/CLAUDE.md index a763b616f..4ae9ebe89 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,7 +19,7 @@ This is an npm workspace monorepo: `docs/` is the source of truth for system-level and process-level knowledge. **"The docs", "check the docs", or "check the X docs" always mean this directory — not the web.** Look here before fetching anything online; the docs capture gotchas and conventions you cannot derive from the code or external sources. -At the start of non-trivial work, list `docs/` and skim anything relevant to the task. When you learn something meta worth preserving — a gotcha, a convention, a workflow, a piece of system context that will outlive the current task — update an existing doc or propose a new one. Code-level facts belong in inline comments next to the code; system, process, and gotcha-level facts belong in `docs/`. +At the start of non-trivial work, list `docs/` and skim anything relevant to the task. | Doc | What's in it | | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | @@ -29,7 +29,7 @@ At the start of non-trivial work, list `docs/` and skim anything relevant to the | [docs/data-model.md](docs/data-model.md) | File-based JSON persistence, Zod schemas, atomic writes, no migrations | | [docs/glossary.md](docs/glossary.md) | Authoritative terminology — UI label wins, no synonyms | | [docs/coding-standards.md](docs/coding-standards.md) | Type hygiene, error handling, state design, React patterns, file organization | -| [docs/design.md](docs/design.md) | Theme tokens — colors, fonts, spacing, radii, icons | +| [docs/design.md](docs/design.md) | Design system — tokens, buttons, hierarchy, density, alignment rails, states, what's forbidden | | [docs/forms.md](docs/forms.md) | Form architecture — non-React form model, form kit, load-state gating; the schedule form is the golden example | | [docs/hover.md](docs/hover.md) | Hover — the canonical pattern (plain View + onPointerEnter/Leave, separate inner Pressable) and the three ways agents break it | | [docs/unistyles.md](docs/unistyles.md) | Unistyles gotchas — `useUnistyles()` is forbidden, alternatives in order | @@ -42,9 +42,11 @@ At the start of non-trivial work, list `docs/` and skim anything relevant to the | [docs/service-proxy.md](docs/service-proxy.md) | Service proxy: exposing workspace scripts at public URLs, DNS setup, reverse proxy config | | [docs/development.md](docs/development.md) | Dev server, build sync gotchas, CLI reference, agent state, Playwright MCP | | [docs/rpc-namespacing.md](docs/rpc-namespacing.md) | WebSocket RPC naming convention — dotted namespaces and `.request`/`.response` pairs | +| [docs/protocol-compatibility.md](docs/protocol-compatibility.md) | Why app/daemon versions drift, protocol vs feature contract, capability gating, COMPAT tagging | | [docs/protocol-validation.md](docs/protocol-validation.md) | zod-aot generated inbound WebSocket validation, patched compiler regressions, schema-purity rules | | [docs/terminal-performance.md](docs/terminal-performance.md) | Terminal latency pipeline, coalescing/backpressure invariants, benchmark + perf spec usage | | [docs/testing.md](docs/testing.md) | TDD workflow, determinism, real dependencies over mocks, test organization | +| [docs/qa.md](docs/qa.md) | QA evidence bar for pull requests — platform matrix, version drift, performance, UI proof | | [docs/mobile-testing.md](docs/mobile-testing.md) | Maestro and mobile test workflows | | [docs/mobile-panels.md](docs/mobile-panels.md) | Compact left/center/right panel ownership, worklet motion, gesture revisions, and Fabric constraints | | [docs/ad-hoc-daemon-testing.md](docs/ad-hoc-daemon-testing.md) | Isolated in-process daemon test harness | @@ -55,6 +57,30 @@ At the start of non-trivial work, list `docs/` and skim anything relevant to the | [docs/terminal-activity.md](docs/terminal-activity.md) | Terminal activity indicators — source-agnostic tracker, agent hook reporting, adding a new hook provider | | [SECURITY.md](SECURITY.md) | Relay threat model, E2E encryption, DNS rebinding, agent auth | +### Writing docs + +- **Integrate, don't append.** Find the doc that owns the subject and rewrite the part that is now wrong. The standard failure is finishing a task and adding a paragraph to the bottom of the closest-looking doc; ten tasks later the doc is a pile of paragraphs in discovery order. `docs/custom-providers.md` is what that looks like. +- **Don't document logic.** Prose that restates code drifts from the code and loses. Write down what the code can't tell you: why something is shaped the way it is, the gotcha that cost an afternoon, conventions nothing enforces, constraints that span packages or versions. If a reader could get it in two minutes by opening the file, cut it. +- **One fact, one doc.** Every other mention is a link. If you are about to write the same paragraph in two docs, one of them is a link. +- **Respect the layers.** `CONTRIBUTING.md` and this file name things and link out. Activity docs like `docs/qa.md` and `docs/testing.md` set the bar for a kind of work. Subject docs like `docs/unistyles.md` own one thing completely. A layer never re-explains the one below it. +- **One subject per doc.** If the subject doesn't fit in a sentence, split the doc. A section per provider, vendor, or platform is a table plus one worked example. +- **Delete.** Obsolete sections go. Prefer a `packages/app/src/thing.ts:120` reference over a pasted block. +- **New doc?** Add a row to the table above and link it from the docs that should send readers there. +- Code-level facts belong in comments next to the code, not here. + +### Doc voice + +Plain and short. Second person. State the rule, then the reason when the reason isn't obvious. Match the doc you're editing. + +Do not: + +- Write a sentence to land a point. "It's not X, it's Y", "That's not a Z, that's a W", and every other setup-and-punchline shape. +- Add a clause that only asserts importance: "and that matters", "which is what keeps it working", "this is critical". +- Use "honest", "robust", "seamless", "powerful", "simply", "just", "delightful". +- Restate something you already said, in different words, for emphasis. +- Hedge with "generally", "typically", or "you may want to" when the answer is "do this". +- Clear your throat: "It's worth noting that", "In order to", "This section covers". + ## Quick start ```bash @@ -94,23 +120,11 @@ See [docs/development.md](docs/development.md) for full setup, build sync requir - **Always use npm scripts for linting and formatting.** Do not run tools directly with `npx eslint`, `npx oxfmt`, `npx oxlint`, or package-local binaries. For targeted checks, pass file paths through the npm script: - `npm run lint -- packages/app/src/components/message.tsx` - `npm run format:files -- CLAUDE.md packages/app/src/components/message.tsx` -- **The protocol stays backward-compatible. Features don't have to.** Two separate contracts: - - **Protocol contract (always):** schema changes must not break parsing in either direction. An old client must still parse messages from a new daemon; a new daemon must still parse messages from an old client. - - New fields: `.optional()` with a sensible default. - - Never flip optional → required, remove fields, or narrow types (`string` → `enum`, `nullable` → non-null). - - Removed fields stay accepted (we stop sending them, not stop reading them). - - Test with: "does a 6-month-old client still parse this?" and "does a 6-month-old daemon still send something this client accepts?" - - Wire schemas are pure structural declarations. Do not add `.transform()`, `.catch()`, or `.preprocess()` to WebSocket message schemas; put normalization in an explicit post-validation pass. - - Plain `z.union()` is forbidden when every branch has a shared literal tag. Use `z.discriminatedUnion()` unless generated-code regression tests prove that specific shape is miscompiled. - - `.default()` is acceptable on primitive leaves only. Never put defaults on item schemas for large arrays or big inbound containers. - - **Feature contract (per-feature):** a new feature may require a new daemon capability. The client detects whether the capability is present and either runs the feature or shows "Update the host to use this." That's it. - - **No fallback paths.** Don't write a degraded version of a new feature that runs on old daemons. Don't fan out across legacy RPCs to simulate a missing capability. The user upgrades or doesn't get the feature. - - **No defensive branches scattered through the feature.** Capability detection happens in one place; downstream code reads a clean shape. - - **Capability flags live in `server_info.features.*`** with a single `// COMPAT(featureName): added in v0.1.X, drop the gate when floor >= v0.1.X` comment marking the cleanup site. - - Existing functionality keeps working across versions — that's the protocol contract doing its job. New-feature degradation is not the goal. - - **New RPCs use dotted namespaces with direction suffixes.** Follow [docs/rpc-namespacing.md](docs/rpc-namespacing.md): `domain.provider.operation.request` pairs with `domain.provider.operation.response`. Existing flat RPC names will migrate over time; don't add new ones. - -- **All back-compat shims are tagged and dated for cleanup.** Every shim that exists for old-client/old-daemon support carries a `COMPAT(name)` comment with the version it was added in and a target removal date (typically 6 months out). One grep — `rg "COMPAT\("` — should produce the full list of cleanup work. Don't bury back-compat in untagged `??`-fallbacks or optional-chain tunnels — that's how it stops being deletable. +- **The protocol stays backward-compatible. Features don't have to.** Read [docs/protocol-compatibility.md](docs/protocol-compatibility.md) before touching `packages/protocol`. The short version: + - **Protocol contract (always):** an old client parses messages from a new daemon, and a new daemon parses messages from an old client. New fields are optional; never narrow, never remove, never require. Wire schemas stay pure — no `.transform()`, `.catch()`, or `.preprocess()`. + - **Feature contract (per-feature):** gate the capability once on `server_info.features.*`, then run the feature or tell the user to update the host. No fallback paths, no defensive branches. + - **Every shim is tagged.** `// COMPAT(name): added in vX, remove after ` at the site that has to be deleted. `rg "COMPAT\("` is the cleanup backlog; untagged back-compat is permanent by accident. + - **New RPCs use dotted namespaces with direction suffixes.** Follow [docs/rpc-namespacing.md](docs/rpc-namespacing.md): `domain.provider.operation.request` pairs with `domain.provider.operation.response`. Existing flat RPC names will migrate over time; don't add new ones. ## Platform gating diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index feb2027c2..c49905005 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,56 +1,148 @@ # Contributing to Paseo -Paseo is an opinionated product maintained by one person right now. +Thank you for taking the time to contribute to Paseo. -The product covers a lot of surface: mobile, desktop, web, the daemon, the relay, and both self-hosted and hosted setups. +## Philosophy -Contributing takes a lot of context that is very hard to transfer. That's why product, design, architecture, and workflow decisions are currently all made by the maintainer. +Paseo is an opinionated product, built on freedom and flexibility: any agent provider, any device, running on your own machine. It is meant to be composable, so you can build the workflow you want. Read more about the product vision [here](docs/product.md). -## Becoming a maintainer +Given Paseo's scope, contributing to to it takes a lot of context that is very hard to transfer. That's why product, design, architecture, and workflow decisions are currently all made by the maintainer. -There's no formal process to become a maintainer, if you consistently contribute and help out, you'll become one. +I pick what to build based on whether it fits the product, how many workflows it improves, whether it keeps things composable, whether we can hold the quality bar, and whether I want to build it. -Here's the progression: +## Submit bugs in GitHub issues or Discord -1. Get involved in the community: answer questions in Discord and on GitHub -2. Triage bugs: replicate and help fix them -3. Work on maintainer-approved features +Open an [issue](https://github.com/getpaseo/paseo/issues) or post in [Discord](https://discord.gg/jz8T2uahpH). -The reason for this progression is so that you can gain all the context you need to take on more responsibility, so that I can see if you have what it takes to be a maintainer. +> [!IMPORTANT] +> Feature requests opened as issues will get closed automatically -Learning on the job is fine, I do not care how many years of experience you have, what I care about is that you get the vision and want to contribute. +Include: + +- What you did, what you expected, what happened instead +- Steps to reproduce +- Paseo version, OS, and agent provider +- Logs, command output, screenshots. A video if it's about interaction, animation, or timing +- Redact secrets, keep the technical details + +Try the latest release first and search for the same symptom. You can submit a pull request with the fix, it's not required. + +If you used an agent to investigate, submit the raw evidence like logs, commands, and repro steps it collected. + +Do not just submit your agent's findings, those usually don't have enough information, and the diagnosis is likely to be incorrect. + +## Start a product discussion in GitHub Discussions or Discord + +There's no feature request backlog. Open a product discussion in [Discussions](https://github.com/getpaseo/paseo/discussions) or `#product` in [Discord](https://discord.gg/jz8T2uahpH), and frame it as a workflow: + +- What are you trying to do? +- How do you do it today? +- Where does Paseo get in the way? +- What would the flow look like if it worked for you? + +"Can you build X" is not useful because it doesn't tell me about your problem. + +A discussion also lets other people add their own version of the same problem, and that's what I read when I decide what to build. + +Due to the volume and my limited bandwidth, I may not participate in all the discussions, if something gains interest it will definitely make it's way to me. ## Pull requests -✅ Will be accepted +Anyone can open a pull request, but there are no guarantees of it getting merged, only submit one if you're okay with it being closed. -- Keep it to one focused change -- Link to an issue -- Explain the problem you're solving -- Include repro steps if it's a bug -- Include QA/testing evidence -- UI changes need screenshots or video for every affected platform: iOS, Android, desktop, and web -- If you only tested one platform, say that clearly +Here is the criteria I use to decide: + +✅ Likely to be accepted + +- Fits the product vision +- One focused change +- Links the bug it fixes, or the discussion behind it if there is one +- Addresses bot reviews +- Explains the problem you're solving +- QA evidence +- Automated tests +- Screenshots or video for UI changes, on every affected platform +- Says which platforms you tested and which you didn't +- Maintainer edits enabled ⛔️ Will be rejected -- Bundle unrelated changes -- Fail basic checks like typecheck, formatting or linting -- Add a feature or design change that wasn't discussed first -- Submit no evidence of testing -- Skip the linked issue +- Bundles unrelated changes +- Fails checks +- Ignores bot reviews +- Takes a feature or the design in a direction I don't want +- No QA evidence +- No tests - Clearly fully AI-generated PR -## Requesting features +### What to expect -If you need a feature implemented, create a Github issue or a thread in Discord. +- PRs that were explicitly approved in a discussion are preferred. +- Unsolicited PR can be closed without a detailed review. +- Your PR can be narrowed, refactored, or redesigned. +- Your PR might be accpeted but not merged immediately. +- You wil be attributed for your work in the changelog, even if I redesign your PR -Explain the problem you want to solve: your use case, where Paseo falls short today, and the flow you expect. +## QA evidence -## AI assistance +QA is the main bottleneck of Paseo's product development, and it's not just whether the feature works, it's also whether it meets the quality bar. -Using AI to help write code is fine, but you must: +Pull requests without evidence will be closed. -- Ensure your agents read the docs -- Understand the code you submit -- Review and test the code yourself +What's accepted: + +- The shell commands you ran, pasted with their output +- The tests you added and their results +- Before and after screenshots +- A video of the whole interaction +- Logs, requests, responses + +Bugs and features both need automated tests, and the tests have to exercise the real thing. UI changes need a video, or a screenshot if it's static. + +The [QA guide](docs/qa.md) covers what to check for each of these and the tooling to do it. + +## I want to become a maintainer + +There's no formal process to become a maintainer, just consistently get involved with the project by answering questions, testing, reproducing bugs, discussing workflows with the community and offer to implement features when we reach an agreement. + +## FAQ + +### I'm blocked, where do I go? + +Join [Discord](https://discord.gg/jz8T2uahpH), ask your questions there. + +### Do you prefer Github or Discord? + +I am more active on Discord for general questions. + +I prefer Github for bugs because the template helps collect information more efficiently. + +### Why was my feature request closed? + +Issues are only for bugs. Open it as a product discussion instead, framed as a workflow. + +### Why didn't my PR get a detailed review? + +Writing a review is very time consuming and Paseo receives a lot of PRs, I cannot give detailed explanations at volume. + +### How long until someone looks at my pull request? + +It depends on what else is going on. QA takes longer than reading the diff. You are welcome to join Discord to ask. + +### Why do you want tests and a video for a small change? + +Because it's faster and safer than me checking out and running every PR myself to test it manually. + +QA evidence help me take your PR seriously for a deeper review and maintainer QA. + +### I only have one platform, can I still contribute? + +Yes. Test what you have, and say what you didn't test. + +### Can I use an agent to write my contribution? + +Yes, as long as you understand what you're submitting and you tested it yourself. + +### Is there a roadmap I can read? + +[docs/product.md](docs/product.md) covers the direction. There's no public list of what I'm building next. diff --git a/docs/design.md b/docs/design.md index 790465389..028989426 100644 --- a/docs/design.md +++ b/docs/design.md @@ -122,7 +122,21 @@ The whitespace is the design. --- -## 8. Responsiveness +## 8. Alignment + +Things align to their glyphs, not to their boxes. A row's leading icon, its title, and the label of the button in its trailing slot sit on the same rails — the ink lines up, not the padding, not the touch target, not the hover background. + +Pick the rails from the content, then hold them. A settings card establishes a leading rail at the icon's left edge and a trailing rail at the last glyph's right edge; every row in that card uses the same two. A row whose icon is absent still starts its title on the leading rail. Indentation is a new rail, not an arbitrary offset. + +The pressable is bigger than the glyph, and that is fine. Hit areas grow outward from the aligned content — they never move it. A button that looks two pixels off because its padding is asymmetric is misaligned even though its box is correct. + +Optical alignment beats arithmetic when a glyph disagrees with its bounding box. Icons with visual weight on one side, chevrons, and single-character labels usually need a small nudge to look centered. Trust the eye, then leave a comment saying the offset is optical. + +One row off the rail makes the whole card look unconsidered. + +--- + +## 9. Responsiveness Compact-first. The small case is designed; the large case adds chrome around it. @@ -143,7 +157,7 @@ A new list+detail feature copies the settings shell. A new workspace-shaped feat --- -## 9. Copy and voice +## 10. Copy and voice Sentence case. "Pair a device", "Danger zone", "Restart daemon", "Inject Paseo tools", "No sessions yet", "Load more". Proper nouns retain casing — Paseo, Beta, Stable, Local. Title case is wrong. @@ -165,7 +179,7 @@ Terminology: --- -## 10. States +## 11. States Loading is inline by default. `` sits next to the thing it relates to (`packages/app/src/screens/settings/providers-section.tsx:227-231`). Page-level loading is a centered `` (`packages/app/src/screens/sessions-screen.tsx:69-72`). Card-level loading is a single short line, not a spinner. In-row dropdown items use ``; the menu item handles its own pending state. @@ -185,9 +199,11 @@ Partial failure (a list mostly fine but one source errored) is a bordered banner State surfaces at the smallest scope it affects. Field error stays under the field; page error is a banner; flow-stopping error is an `Alert`. +Changing state must not move the layout. A row that grows when its badge arrives, a card that reflows when a count resolves, a list that jumps as data streams in — all wrong. Reserve the space the loaded state will need, so the skeleton, the spinner, and the content occupy the same box. A surface that shifts under the user stops feeling calm. + --- -## 11. List rows +## 12. List rows The row anatomy is a content column with an optional trailing slot. Inside a card the row is `settingsStyles.row`. Inside a sidebar list the row carries its own padding and `borderRadius.lg` per item (`packages/app/src/components/sidebar-workspace-list.tsx:2614-2625`). @@ -203,7 +219,7 @@ Selected state on rows in a desktop list+detail uses `surfaceSidebarHover` as th --- -## 12. Status pills and badges +## 13. Status pills and badges Status pills are `palette.[300]` foreground on a 10%-alpha background of the same color. Success uses green, warning uses amber, danger uses red, muted uses zinc. The `` primitive (`packages/app/src/components/ui/status-badge.tsx`) is canonical. @@ -213,7 +229,7 @@ The bespoke pills in `packages/app/src/screens/settings/host-page.tsx:97-116`, ` --- -## 13. Forbidden +## 14. Forbidden - `fontWeight.medium` on row titles, body text, button labels, badge text, or `` titles. Medium is reserved for the structural-label tier described in §3 — section labels, modal/sheet titles, dense metadata emphasis, and tight action labels. Anything else is `normal`. `` is responsive `400/300` and is never overridden. - `` wrapping `` to make a button. `