wasp/web/blog/2021-12-02-waspello.md

217 lines
12 KiB
Markdown
Raw Normal View History

---
title: How we built a Trello clone with Wasp - Waspello!
authors: [matijasos]
tags: [webdev, wasp]
---
import Link from '@docusaurus/Link';
import useBaseUrl from '@docusaurus/useBaseUrl';
import InBlogCta from './components/InBlogCta';
import WaspIntro from './_wasp-intro.md';
import ImgWithCaption from './components/ImgWithCaption'
![Enter Waspello](../static/img/waspello-screenshot.png)
<p align="center">
<Link to={'https://waspello.netlify.app/'}>Try Waspello here!</Link> | <Link to={'https://github.com/wasp-lang/wasp/blob/release/examples/waspello/main.wasp'}>See the code</Link>
</p>
We've built a Trello clone using Wasp! Read on to learn how it went and how you can contribute.
<!--truncate-->
<WaspIntro />
<InBlogCta />
## Why Trello?
While building Wasp, our goal is to use it as much as we can to build our projects and play with it, so we can learn what works and what we should do next. This is why Trello was a great choice of app to build with Wasp - it is one of the most well-known full-stack web apps, it's very simple and intuitive to use but also covers a good portion of features used by today's modern web apps.
So let's dig in and see and how it went - what works, what doesn't and, what's missing/coming next!
## What works?
### It's alive ⚡🤖 !!
The good news is all the basic functionality is here - Waspello users can signup/log in which brings them to their project board where they can perform CRUD operations on lists and cards - create them, edit them, move them around, etc. Let's see it in action:
![Waspello in action](../static/img/waspello-in-action.gif)
<p align="center" class="image-caption">
Waspello in action!
</p>
As you can see things work, but not everything is perfect (e.g. there is a delay when creating/moving a card) - we'll examine why is that so a bit later.
### Under the hood 🚘 🔧
Here is a simple visual overview of Waspello's code anatomy (which applies to every Wasp app):
![Waspello code anatomy](../static/img/waspello-code-anatomy.png)
<p align="center" class="image-caption">
Waspello code anatomy
</p>
Let's now dig in a bit deeper and shortly examine each of the concepts Wasp supports (page, query, entity, ...) and learn through code samples how to use it to implement Waspello.
#### Entities
It all starts with a data model definition (called `entity` in Wasp), which is defined via Prisma Schema Language:
```js title="main.wasp | Defining entities via Prisma Schema Language"
// Entities
entity User {=psl
id Int @id @default(autoincrement())
username String @unique
password String
lists List[]
cards Card[]
psl=}
entity List {=psl
id Int @id @default(autoincrement())
name String
pos Float
// List has a single author.
user User @relation(fields: [userId], references: [id])
userId Int
cards Card[]
psl=}
entity Card {=psl
id Int @id @default(autoincrement())
title String
pos Float
// Card belongs to a single list.
list List @relation(fields: [listId], references: [id])
listId Int
// Card has a single author.
author User @relation(fields: [authorId], references: [id])
authorId Int
psl=}
```
Those three entities are all we need! Wasp uses Prisma to create a database schema underneath and allows the developer to query it through its generated SDK.
#### Queries and Actions (Operations)
After we've defined our data models, the next step is to do something with them! We can read/create/update/delete an entity and that is what `query` and `action` mechanisms are for. Below follows an example from the Waspello code that demonstrates how it works.
The first step is to declare to Wasp there will be a query, point to the actual function containing the query logic, and state from which entities it will be reading information:
```js title="main.wasp | Declaration of a query in Wasp"
query getListsAndCards {
// Points to the function which contains query logic.
Adapt example apps to project struct change (#809) * Separate ext code to client and server * Use skeleton in createNewProject and refactor * Refactor Lib.hs to use ExceptT * Fix formatting * Pop up returns * Extract liftIO and add a do block Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Address some review comments * Add skeleton comment * Extract common CommandError message * Separate skeleton comment into two rows * Move server and client dirs into src * Simplify maybeToEither * Further refactor Lib.hs * Further simplify skeleton comment * Add shared code directory to project structure * Update e2e test inputs * Update e2e test outputs * Fix formatting * Fix bug in compile function Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com> * Change map to fmap in compile function * Fix formatting * Force git to include empty directories * Remove extra empty line from .gitkeep files * Add .jsconfig to enable go-to-definition * Watch shared directory for changes * Add final newline to jsconfigs * Fix regular and e2e tests * Update e2e tests * Fix cli template packaging and update todoApp * Add a shared function demo to todoApp * Update waspc and e2e tests * Fix compiler warnings and rename function * Rename mkError to mkParserError * Remove redundant empty line * Fix test warnings * Fix formatting * Update waspc.cabal with jsconfigs * Minimize jsconfig.json files * Add jsconfigs to waspc todoApp * Update e2e tests * Update e2e tests * Update docs for new project structure * Update todoApp example to new structure * Update ItWaspsOnMyMachine * Update thoughts * Update Waspello to new structure * Update Waspleau to new structure * Update Realworld to new structure, fix warnings * Fix directory tree watching on wasp start * Implement review feedback * Fix typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Fix typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Fix another typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Add src prefix to file path in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Remove env server file * Apply suggestions from code review Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Address code review comments * Fix incorrect path in docs * Fix references to ext in docs * Edit changelog and add migration guide * Further update docs to new structure * Update blogs about example apps to new structure * Fix typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Update typo on frontpage Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Add missing src in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com>
2022-11-11 20:58:29 +03:00
fn: import { getListsAndCards } from "@server/queries.js",
// This query depends on List and Card entities.
// If any of them changes this query will get re-fetched (cache invalidation).
entities: [List, Card]
}
```
The main point of this declaration is for Wasp to be aware of the query and thus be able to **do a lot of heavy lifting for us - e.g. it will make the query available to the client without any extra code**, all that developer needs to do is import it in their React component. **Another big thing is cache invalidation / automatic re-fetching of the query once the data changes** (this is why it is important to declare which entities it depends on).
The remaining step is to write the function with the query logic:
Adapt example apps to project struct change (#809) * Separate ext code to client and server * Use skeleton in createNewProject and refactor * Refactor Lib.hs to use ExceptT * Fix formatting * Pop up returns * Extract liftIO and add a do block Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Address some review comments * Add skeleton comment * Extract common CommandError message * Separate skeleton comment into two rows * Move server and client dirs into src * Simplify maybeToEither * Further refactor Lib.hs * Further simplify skeleton comment * Add shared code directory to project structure * Update e2e test inputs * Update e2e test outputs * Fix formatting * Fix bug in compile function Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com> * Change map to fmap in compile function * Fix formatting * Force git to include empty directories * Remove extra empty line from .gitkeep files * Add .jsconfig to enable go-to-definition * Watch shared directory for changes * Add final newline to jsconfigs * Fix regular and e2e tests * Update e2e tests * Fix cli template packaging and update todoApp * Add a shared function demo to todoApp * Update waspc and e2e tests * Fix compiler warnings and rename function * Rename mkError to mkParserError * Remove redundant empty line * Fix test warnings * Fix formatting * Update waspc.cabal with jsconfigs * Minimize jsconfig.json files * Add jsconfigs to waspc todoApp * Update e2e tests * Update e2e tests * Update docs for new project structure * Update todoApp example to new structure * Update ItWaspsOnMyMachine * Update thoughts * Update Waspello to new structure * Update Waspleau to new structure * Update Realworld to new structure, fix warnings * Fix directory tree watching on wasp start * Implement review feedback * Fix typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Fix typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Fix another typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Add src prefix to file path in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Remove env server file * Apply suggestions from code review Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Address code review comments * Fix incorrect path in docs * Fix references to ext in docs * Edit changelog and add migration guide * Further update docs to new structure * Update blogs about example apps to new structure * Fix typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Update typo on frontpage Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Add missing src in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com>
2022-11-11 20:58:29 +03:00
```js title="src/server/queries.js | Query logic, using Prisma SDK via Node.js"
export const getListsAndCards = async (args, context) => {
// Only authenticated users can execute this query.
if (!context.user) { throw new HttpError(403) }
return context.entities.List.findMany({
// We want to make sure user can access only their own cards.
where: { user: { id: context.user.id } },
include: { cards: true }
})
}
```
This is just a regular Node.js function, there are no limits on what you can return! All the stuff provided by Wasp (user data, Prisma SDK for a specific entity) comes in a `context` variable.
The code for actions is very similar (we just need to use `action` keyword instead of `query`) so I won't repeat it here. You can check out the code for `updateCard` action [here](https://github.com/wasp-lang/wasp/blob/release/examples/waspello/main.wasp#L103).
#### Pages, routing & components
To display all the nice data we have, we'll use React components. There are no limits to how you can use React components within Wasp, the only one is that each `page` has its root component:
```js title="main.wasp | Declaration of a page & route in Wasp"
route MainRoute { path: "/", to: Main }
page Main {
authRequired: true,
Adapt example apps to project struct change (#809) * Separate ext code to client and server * Use skeleton in createNewProject and refactor * Refactor Lib.hs to use ExceptT * Fix formatting * Pop up returns * Extract liftIO and add a do block Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Address some review comments * Add skeleton comment * Extract common CommandError message * Separate skeleton comment into two rows * Move server and client dirs into src * Simplify maybeToEither * Further refactor Lib.hs * Further simplify skeleton comment * Add shared code directory to project structure * Update e2e test inputs * Update e2e test outputs * Fix formatting * Fix bug in compile function Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com> * Change map to fmap in compile function * Fix formatting * Force git to include empty directories * Remove extra empty line from .gitkeep files * Add .jsconfig to enable go-to-definition * Watch shared directory for changes * Add final newline to jsconfigs * Fix regular and e2e tests * Update e2e tests * Fix cli template packaging and update todoApp * Add a shared function demo to todoApp * Update waspc and e2e tests * Fix compiler warnings and rename function * Rename mkError to mkParserError * Remove redundant empty line * Fix test warnings * Fix formatting * Update waspc.cabal with jsconfigs * Minimize jsconfig.json files * Add jsconfigs to waspc todoApp * Update e2e tests * Update e2e tests * Update docs for new project structure * Update todoApp example to new structure * Update ItWaspsOnMyMachine * Update thoughts * Update Waspello to new structure * Update Waspleau to new structure * Update Realworld to new structure, fix warnings * Fix directory tree watching on wasp start * Implement review feedback * Fix typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Fix typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Fix another typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Add src prefix to file path in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Remove env server file * Apply suggestions from code review Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Address code review comments * Fix incorrect path in docs * Fix references to ext in docs * Edit changelog and add migration guide * Further update docs to new structure * Update blogs about example apps to new structure * Fix typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Update typo on frontpage Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Add missing src in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com>
2022-11-11 20:58:29 +03:00
component: import Main from "@client/MainPage.js"
}
```
All pretty straightforward so far! As you can see here, Wasp also provides [authentication out-of-the-box](/docs/language/features#authentication--authorization).
Adapt example apps to project struct change (#809) * Separate ext code to client and server * Use skeleton in createNewProject and refactor * Refactor Lib.hs to use ExceptT * Fix formatting * Pop up returns * Extract liftIO and add a do block Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Address some review comments * Add skeleton comment * Extract common CommandError message * Separate skeleton comment into two rows * Move server and client dirs into src * Simplify maybeToEither * Further refactor Lib.hs * Further simplify skeleton comment * Add shared code directory to project structure * Update e2e test inputs * Update e2e test outputs * Fix formatting * Fix bug in compile function Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com> * Change map to fmap in compile function * Fix formatting * Force git to include empty directories * Remove extra empty line from .gitkeep files * Add .jsconfig to enable go-to-definition * Watch shared directory for changes * Add final newline to jsconfigs * Fix regular and e2e tests * Update e2e tests * Fix cli template packaging and update todoApp * Add a shared function demo to todoApp * Update waspc and e2e tests * Fix compiler warnings and rename function * Rename mkError to mkParserError * Remove redundant empty line * Fix test warnings * Fix formatting * Update waspc.cabal with jsconfigs * Minimize jsconfig.json files * Add jsconfigs to waspc todoApp * Update e2e tests * Update e2e tests * Update docs for new project structure * Update todoApp example to new structure * Update ItWaspsOnMyMachine * Update thoughts * Update Waspello to new structure * Update Waspleau to new structure * Update Realworld to new structure, fix warnings * Fix directory tree watching on wasp start * Implement review feedback * Fix typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Fix typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Fix another typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Add src prefix to file path in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Remove env server file * Apply suggestions from code review Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Address code review comments * Fix incorrect path in docs * Fix references to ext in docs * Edit changelog and add migration guide * Further update docs to new structure * Update blogs about example apps to new structure * Fix typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Update typo on frontpage Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Add missing src in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com>
2022-11-11 20:58:29 +03:00
Currently, the majority of the client logic of Waspello is contained in `src/client/MainPage.js` (we should break it down a little 😅 - [you can help us!](https://github.com/wasp-lang/wasp/issues/334)). Just to give you an idea, here's a quick glimpse into it:
Adapt example apps to project struct change (#809) * Separate ext code to client and server * Use skeleton in createNewProject and refactor * Refactor Lib.hs to use ExceptT * Fix formatting * Pop up returns * Extract liftIO and add a do block Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Address some review comments * Add skeleton comment * Extract common CommandError message * Separate skeleton comment into two rows * Move server and client dirs into src * Simplify maybeToEither * Further refactor Lib.hs * Further simplify skeleton comment * Add shared code directory to project structure * Update e2e test inputs * Update e2e test outputs * Fix formatting * Fix bug in compile function Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com> * Change map to fmap in compile function * Fix formatting * Force git to include empty directories * Remove extra empty line from .gitkeep files * Add .jsconfig to enable go-to-definition * Watch shared directory for changes * Add final newline to jsconfigs * Fix regular and e2e tests * Update e2e tests * Fix cli template packaging and update todoApp * Add a shared function demo to todoApp * Update waspc and e2e tests * Fix compiler warnings and rename function * Rename mkError to mkParserError * Remove redundant empty line * Fix test warnings * Fix formatting * Update waspc.cabal with jsconfigs * Minimize jsconfig.json files * Add jsconfigs to waspc todoApp * Update e2e tests * Update e2e tests * Update docs for new project structure * Update todoApp example to new structure * Update ItWaspsOnMyMachine * Update thoughts * Update Waspello to new structure * Update Waspleau to new structure * Update Realworld to new structure, fix warnings * Fix directory tree watching on wasp start * Implement review feedback * Fix typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Fix typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Fix another typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Add src prefix to file path in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Remove env server file * Apply suggestions from code review Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Address code review comments * Fix incorrect path in docs * Fix references to ext in docs * Edit changelog and add migration guide * Further update docs to new structure * Update blogs about example apps to new structure * Fix typo in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Update typo on frontpage Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> * Add missing src in docs Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> Co-authored-by: Shayne Czyzewski <523636+shayneczyzewski@users.noreply.github.com> Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com>
2022-11-11 20:58:29 +03:00
```js title="src/client/MainPage.js | Using React component in Wasp"
// "Special" imports provided by Wasp.
import { useQuery } from '@wasp/queries'
import getListsAndCards from '@wasp/queries/getListsAndCards'
import createList from '@wasp/actions/createList'
const MainPage = ({ user }) => {
// Fetching data via useQuery.
const { data: listsAndCards, isFetchingListsAndCards, errorListsAndCards }
= useQuery(getListsAndCards)
// A lot of data transformations and sub components.
...
// Display lists and cards.
return (
...
)
}
```
Once you've defined a query or action as described above, you can immediately import it into your client code as shown in the code sample, by using the `@wasp` prefix in the import path. `useQuery` ensures reactivity so once the data changes the query will get re-fetched. You can find more details about it [here](/docs/language/features#usequery).
This is pretty much it from the stuff that works 😄 ! I kinda rushed a bit through things here - for more details on all Wasp features and to build your first app with Wasp, check out our [docs](/docs/).
## What doesn't work (yet)
The main problem of the current implementation of Waspello is the **lack of support for optimistic UI updates in Wasp**. What this means is that currently, when an entity-related change is made (e.g. a card is moved from one list to another), we have to wait until that change is fully executed on the server until it is visible in the UI, which causes a noticeable delay.
In many cases that is not an issue, but when UI elements are all visible at once and it is expected from them to be updated immediately, then it is noticeable. This is also one of the main reasons why we chose to work on Waspello - to have a benchmark/sandbox for this feature! Due to this issue, here's how things currently look like:
<ImgWithCaption alt="Waspello - no optimistic UI update" source="img/waspello-no-opt-UI-updates.gif" caption="Without an optimistic UI update, there is a delay"/>
You can notice the delay between the moment the card is dropped on the "Done" list and the moment it becomes a part of that list. The reason is that at the moment of dropping the card on "Done" list, the API request with the change is sent to the server, and only when that change is fully processed on the server and saved to the database, the query `getListsAndCards` returns the correct info and consequently, UI is updated to the correct state.
That is why upon dropping on "Done", the card first goes back to the original list (because the change is not saved in db yet, so `useQuery(getListsAndCards)` still returns the "old" state), it waits a bit until the API request is processed successfully, and just then the change gets reflected in the UI.
#### The solution
A typical approach for solving this issue is to **make the client a bit more self-confident, in a way that it doesn't wait for the confirmation from the server but rather immediately updates the UI, at the same time or even before the API request is fired**. If it then turns out something went wrong on the server (which typically shouldn't happen), it reverses the change and shows an error message. Thus the name optimistic UI update, since the client assumes in advance that everything will go well to provide a nicer UX.
<p align="center">
<figure>
<img alt="Waspello - the client being brave"
src={useBaseUrl('img/waspello-client-being-brave.gif')}
/>
<figcaption class="image-caption">The client when performing an optimistic UI update</figcaption>
</figure>
</p>
This is one of the most complex and error-prone features when developing web apps today and that is why we are super excited to tackle it in Wasp and make the experience as smooth as possible! We are currently in the "figuring out the solution" stage and you can [track/join the discussion on GitHub](https://github.com/wasp-lang/wasp/issues/63)!
## What's missing (next features)
Although it looks super simple at the first glance, Trello is in fact a huge app with lots and lots of cool features hidden under the surface! Here are some of the more obvious ones that are currently not supported in Waspello:
- **Users can have multiple boards**, for different projects (currently we have no notion of a "Board" entity in Waspello at all, so there is implicitly only one)
- **Detailed card view** - when clicked on a card, a "full" view with extra options opens
- **Search** - user can search for a specific list/card
- **Collaboration** - multiple users can participate on the same board
And many more - e.g. support for workspaces (next level of the hierarchy, a collection of boards), card labels, filters, ... . It is very helpful to have such a variety of features since we can use it as a testing ground for Wasp and use it as a guiding star towards Beta/1.0!
## Become a Waspeller!
<p align="center" >
<figure style={{width: '55%'}}>
<img alt="Waspello propaganda"
src={useBaseUrl('img/waspello-propaganda.png')}
/>
<figcaption class="image-caption">Lightweight Waspello propaganda</figcaption>
</figure>
</p>
If you want to get involved with OSS and at the same time familiarize yourself with Wasp, this is a great way to get started - feel free to [choose one of the features listed here or add your own](https://github.com/wasp-lang/wasp/issues/337) and help us make Waspello the best demo productivity app out there!
Also, make sure to [join our community on Discord](https://discord.gg/rzdnErX). Were always there and are looking forward to seeing what you build!
<InBlogCta />