+6
.formatter.exs
+6
.formatter.exs
+19
-10
.gitignore
+19
-10
.gitignore
···
1
1
node_modules
2
-
3
-
# Output
4
2
.output
5
3
.vercel
6
4
.netlify
7
5
.wrangler
8
-
/.svelte-kit
9
-
/build
10
-
11
-
# OS
6
+
.svelte-kit
7
+
build
8
+
.elixir_ls
12
9
.DS_Store
13
10
Thumbs.db
14
-
15
-
# Env
16
11
.env
17
12
.env.*
18
13
!.env.example
19
14
!.env.test
20
15
.envrc
21
16
.direnv
22
-
23
-
# Vite
24
17
vite.config.js.timestamp-*
25
18
vite.config.ts.timestamp-*
19
+
result
20
+
/_build/
21
+
/cover/
22
+
/deps/
23
+
/doc/
24
+
/.fetch
25
+
erl_crash.dump
26
+
*.ez
27
+
/tmp/
28
+
comet-*.tar
29
+
/priv/static/assets/
30
+
/priv/static/hologram/
31
+
/priv/static/cache_manifest.json
32
+
npm-debug.log
33
+
/assets/node_modules/
34
+
+2
-10
.prettierrc
+2
-10
.prettierrc
···
1
1
{
2
-
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
2
+
"plugins": ["prettier-plugin-tailwindcss"],
3
3
"proseWrap": "always",
4
-
"htmlWhitespaceSensitivity": "ignore",
5
-
"overrides": [
6
-
{
7
-
"files": "*.svelte",
8
-
"options": {
9
-
"parser": "svelte"
10
-
}
11
-
}
12
-
]
4
+
"htmlWhitespaceSensitivity": "ignore"
13
5
}
+6
-2
.vscode/settings.json
+6
-2
.vscode/settings.json
···
1
1
{
2
2
"json.schemas": [
3
3
{
4
-
"fileMatch": ["/packages/lexicons/*/**/*.json"],
4
+
"fileMatch": ["/lexicons/*/**/*.json"],
5
5
"url": "https://gist.githubusercontent.com/mary-ext/6e428031c18799d1587048b456d118cb/raw/4322c492384ac5da33986dee9588938a88d922f1/schema.json"
6
6
}
7
-
]
7
+
],
8
+
"files.associations": {
9
+
"*.holo": "phoenix-heex"
10
+
},
11
+
8
12
}
+449
AGENTS.md
+449
AGENTS.md
···
1
+
This is a web application written using the Phoenix web framework.
2
+
3
+
## Project guidelines
4
+
5
+
- Use `mix precommit` alias when you are done with all changes and fix any pending issues
6
+
- Use the already included and available `:req` (`Req`) library for HTTP requests, **avoid** `:httpoison`, `:tesla`, and `:httpc`. Req is included by default and is the preferred HTTP client for Phoenix apps
7
+
8
+
### Phoenix v1.8 guidelines
9
+
10
+
- **Always** begin your LiveView templates with `<Layouts.app flash={@flash} ...>` which wraps all inner content
11
+
- The `MyAppWeb.Layouts` module is aliased in the `my_app_web.ex` file, so you can use it without needing to alias it again
12
+
- Anytime you run into errors with no `current_scope` assign:
13
+
- You failed to follow the Authenticated Routes guidelines, or you failed to pass `current_scope` to `<Layouts.app>`
14
+
- **Always** fix the `current_scope` error by moving your routes to the proper `live_session` and ensure you pass `current_scope` as needed
15
+
- Phoenix v1.8 moved the `<.flash_group>` component to the `Layouts` module. You are **forbidden** from calling `<.flash_group>` outside of the `layouts.ex` module
16
+
- Out of the box, `core_components.ex` imports an `<.icon name="hero-x-mark" class="w-5 h-5"/>` component for for hero icons. **Always** use the `<.icon>` component for icons, **never** use `Heroicons` modules or similar
17
+
- **Always** use the imported `<.input>` component for form inputs from `core_components.ex` when available. `<.input>` is imported and using it will save steps and prevent errors
18
+
- If you override the default input classes (`<.input class="myclass px-2 py-1 rounded-lg">)`) class with your own values, no default classes are inherited, so your
19
+
custom classes must fully style the input
20
+
21
+
### JS and CSS guidelines
22
+
23
+
- **Use Tailwind CSS classes and custom CSS rules** to create polished, responsive, and visually stunning interfaces.
24
+
- Tailwindcss v4 **no longer needs a tailwind.config.js** and uses a new import syntax in `app.css`:
25
+
26
+
@import "tailwindcss" source(none);
27
+
@source "../css";
28
+
@source "../js";
29
+
@source "../../lib/my_app_web";
30
+
31
+
- **Always use and maintain this import syntax** in the app.css file for projects generated with `phx.new`
32
+
- **Never** use `@apply` when writing raw css
33
+
- **Always** manually write your own tailwind-based components instead of using daisyUI for a unique, world-class design
34
+
- Out of the box **only the app.js and app.css bundles are supported**
35
+
- You cannot reference an external vendor'd script `src` or link `href` in the layouts
36
+
- You must import the vendor deps into app.js and app.css to use them
37
+
- **Never write inline <script>custom js</script> tags within templates**
38
+
39
+
### UI/UX & design guidelines
40
+
41
+
- **Produce world-class UI designs** with a focus on usability, aesthetics, and modern design principles
42
+
- Implement **subtle micro-interactions** (e.g., button hover effects, and smooth transitions)
43
+
- Ensure **clean typography, spacing, and layout balance** for a refined, premium look
44
+
- Focus on **delightful details** like hover effects, loading states, and smooth page transitions
45
+
46
+
47
+
<!-- usage-rules-start -->
48
+
49
+
<!-- phoenix:elixir-start -->
50
+
## Elixir guidelines
51
+
52
+
- Elixir lists **do not support index based access via the access syntax**
53
+
54
+
**Never do this (invalid)**:
55
+
56
+
i = 0
57
+
mylist = ["blue", "green"]
58
+
mylist[i]
59
+
60
+
Instead, **always** use `Enum.at`, pattern matching, or `List` for index based list access, ie:
61
+
62
+
i = 0
63
+
mylist = ["blue", "green"]
64
+
Enum.at(mylist, i)
65
+
66
+
- Elixir variables are immutable, but can be rebound, so for block expressions like `if`, `case`, `cond`, etc
67
+
you *must* bind the result of the expression to a variable if you want to use it and you CANNOT rebind the result inside the expression, ie:
68
+
69
+
# INVALID: we are rebinding inside the `if` and the result never gets assigned
70
+
if connected?(socket) do
71
+
socket = assign(socket, :val, val)
72
+
end
73
+
74
+
# VALID: we rebind the result of the `if` to a new variable
75
+
socket =
76
+
if connected?(socket) do
77
+
assign(socket, :val, val)
78
+
end
79
+
80
+
- **Never** nest multiple modules in the same file as it can cause cyclic dependencies and compilation errors
81
+
- **Never** use map access syntax (`changeset[:field]`) on structs as they do not implement the Access behaviour by default. For regular structs, you **must** access the fields directly, such as `my_struct.field` or use higher level APIs that are available on the struct if they exist, `Ecto.Changeset.get_field/2` for changesets
82
+
- Elixir's standard library has everything necessary for date and time manipulation. Familiarize yourself with the common `Time`, `Date`, `DateTime`, and `Calendar` interfaces by accessing their documentation as necessary. **Never** install additional dependencies unless asked or for date/time parsing (which you can use the `date_time_parser` package)
83
+
- Don't use `String.to_atom/1` on user input (memory leak risk)
84
+
- Predicate function names should not start with `is_` and should end in a question mark. Names like `is_thing` should be reserved for guards
85
+
- Elixir's builtin OTP primitives like `DynamicSupervisor` and `Registry`, require names in the child spec, such as `{DynamicSupervisor, name: MyApp.MyDynamicSup}`, then you can use `DynamicSupervisor.start_child(MyApp.MyDynamicSup, child_spec)`
86
+
- Use `Task.async_stream(collection, callback, options)` for concurrent enumeration with back-pressure. The majority of times you will want to pass `timeout: :infinity` as option
87
+
88
+
## Mix guidelines
89
+
90
+
- Read the docs and options before using tasks (by using `mix help task_name`)
91
+
- To debug test failures, run tests in a specific file with `mix test test/my_test.exs` or run all previously failed tests with `mix test --failed`
92
+
- `mix deps.clean --all` is **almost never needed**. **Avoid** using it unless you have good reason
93
+
94
+
## Test guidelines
95
+
96
+
- **Always use `start_supervised!/1`** to start processes in tests as it guarantees cleanup between tests
97
+
- **Avoid** `Process.sleep/1` and `Process.alive?/1` in tests
98
+
- Instead of sleeping to wait for a process to finish, **always** use `Process.monitor/1` and assert on the DOWN message:
99
+
100
+
ref = Process.monitor(pid)
101
+
assert_receive {:DOWN, ^ref, :process, ^pid, :normal}
102
+
103
+
- Instead of sleeping to synchronize before the next call, **always** use `_ = :sys.get_state/1` to ensure the process has handled prior messages
104
+
<!-- phoenix:elixir-end -->
105
+
106
+
<!-- phoenix:phoenix-start -->
107
+
## Phoenix guidelines
108
+
109
+
- Remember Phoenix router `scope` blocks include an optional alias which is prefixed for all routes within the scope. **Always** be mindful of this when creating routes within a scope to avoid duplicate module prefixes.
110
+
111
+
- You **never** need to create your own `alias` for route definitions! The `scope` provides the alias, ie:
112
+
113
+
scope "/admin", AppWeb.Admin do
114
+
pipe_through :browser
115
+
116
+
live "/users", UserLive, :index
117
+
end
118
+
119
+
the UserLive route would point to the `AppWeb.Admin.UserLive` module
120
+
121
+
- `Phoenix.View` no longer is needed or included with Phoenix, don't use it
122
+
<!-- phoenix:phoenix-end -->
123
+
124
+
<!-- phoenix:ecto-start -->
125
+
## Ecto Guidelines
126
+
127
+
- **Always** preload Ecto associations in queries when they'll be accessed in templates, ie a message that needs to reference the `message.user.email`
128
+
- Remember `import Ecto.Query` and other supporting modules when you write `seeds.exs`
129
+
- `Ecto.Schema` fields always use the `:string` type, even for `:text`, columns, ie: `field :name, :string`
130
+
- `Ecto.Changeset.validate_number/2` **DOES NOT SUPPORT the `:allow_nil` option**. By default, Ecto validations only run if a change for the given field exists and the change value is not nil, so such as option is never needed
131
+
- You **must** use `Ecto.Changeset.get_field(changeset, :field)` to access changeset fields
132
+
- Fields which are set programatically, such as `user_id`, must not be listed in `cast` calls or similar for security purposes. Instead they must be explicitly set when creating the struct
133
+
- **Always** invoke `mix ecto.gen.migration migration_name_using_underscores` when generating migration files, so the correct timestamp and conventions are applied
134
+
<!-- phoenix:ecto-end -->
135
+
136
+
<!-- phoenix:html-start -->
137
+
## Phoenix HTML guidelines
138
+
139
+
- Phoenix templates **always** use `~H` or .html.heex files (known as HEEx), **never** use `~E`
140
+
- **Always** use the imported `Phoenix.Component.form/1` and `Phoenix.Component.inputs_for/1` function to build forms. **Never** use `Phoenix.HTML.form_for` or `Phoenix.HTML.inputs_for` as they are outdated
141
+
- When building forms **always** use the already imported `Phoenix.Component.to_form/2` (`assign(socket, form: to_form(...))` and `<.form for={@form} id="msg-form">`), then access those forms in the template via `@form[:field]`
142
+
- **Always** add unique DOM IDs to key elements (like forms, buttons, etc) when writing templates, these IDs can later be used in tests (`<.form for={@form} id="product-form">`)
143
+
- For "app wide" template imports, you can import/alias into the `my_app_web.ex`'s `html_helpers` block, so they will be available to all LiveViews, LiveComponent's, and all modules that do `use MyAppWeb, :html` (replace "my_app" by the actual app name)
144
+
145
+
- Elixir supports `if/else` but **does NOT support `if/else if` or `if/elsif`. **Never use `else if` or `elseif` in Elixir**, **always** use `cond` or `case` for multiple conditionals.
146
+
147
+
**Never do this (invalid)**:
148
+
149
+
<%= if condition do %>
150
+
...
151
+
<% else if other_condition %>
152
+
...
153
+
<% end %>
154
+
155
+
Instead **always** do this:
156
+
157
+
<%= cond do %>
158
+
<% condition -> %>
159
+
...
160
+
<% condition2 -> %>
161
+
...
162
+
<% true -> %>
163
+
...
164
+
<% end %>
165
+
166
+
- HEEx require special tag annotation if you want to insert literal curly's like `{` or `}`. If you want to show a textual code snippet on the page in a `<pre>` or `<code>` block you *must* annotate the parent tag with `phx-no-curly-interpolation`:
167
+
168
+
<code phx-no-curly-interpolation>
169
+
let obj = {key: "val"}
170
+
</code>
171
+
172
+
Within `phx-no-curly-interpolation` annotated tags, you can use `{` and `}` without escaping them, and dynamic Elixir expressions can still be used with `<%= ... %>` syntax
173
+
174
+
- HEEx class attrs support lists, but you must **always** use list `[...]` syntax. You can use the class list syntax to conditionally add classes, **always do this for multiple class values**:
175
+
176
+
<a class={[
177
+
"px-2 text-white",
178
+
@some_flag && "py-5",
179
+
if(@other_condition, do: "border-red-500", else: "border-blue-100"),
180
+
...
181
+
]}>Text</a>
182
+
183
+
and **always** wrap `if`'s inside `{...}` expressions with parens, like done above (`if(@other_condition, do: "...", else: "...")`)
184
+
185
+
and **never** do this, since it's invalid (note the missing `[` and `]`):
186
+
187
+
<a class={
188
+
"px-2 text-white",
189
+
@some_flag && "py-5"
190
+
}> ...
191
+
=> Raises compile syntax error on invalid HEEx attr syntax
192
+
193
+
- **Never** use `<% Enum.each %>` or non-for comprehensions for generating template content, instead **always** use `<%= for item <- @collection do %>`
194
+
- HEEx HTML comments use `<%!-- comment --%>`. **Always** use the HEEx HTML comment syntax for template comments (`<%!-- comment --%>`)
195
+
- HEEx allows interpolation via `{...}` and `<%= ... %>`, but the `<%= %>` **only** works within tag bodies. **Always** use the `{...}` syntax for interpolation within tag attributes, and for interpolation of values within tag bodies. **Always** interpolate block constructs (if, cond, case, for) within tag bodies using `<%= ... %>`.
196
+
197
+
**Always** do this:
198
+
199
+
<div id={@id}>
200
+
{@my_assign}
201
+
<%= if @some_block_condition do %>
202
+
{@another_assign}
203
+
<% end %>
204
+
</div>
205
+
206
+
and **Never** do this โ the program will terminate with a syntax error:
207
+
208
+
<%!-- THIS IS INVALID NEVER EVER DO THIS --%>
209
+
<div id="<%= @invalid_interpolation %>">
210
+
{if @invalid_block_construct do}
211
+
{end}
212
+
</div>
213
+
<!-- phoenix:html-end -->
214
+
215
+
<!-- phoenix:liveview-start -->
216
+
## Phoenix LiveView guidelines
217
+
218
+
- **Never** use the deprecated `live_redirect` and `live_patch` functions, instead **always** use the `<.link navigate={href}>` and `<.link patch={href}>` in templates, and `push_navigate` and `push_patch` functions LiveViews
219
+
- **Avoid LiveComponent's** unless you have a strong, specific need for them
220
+
- LiveViews should be named like `AppWeb.WeatherLive`, with a `Live` suffix. When you go to add LiveView routes to the router, the default `:browser` scope is **already aliased** with the `AppWeb` module, so you can just do `live "/weather", WeatherLive`
221
+
222
+
### LiveView streams
223
+
224
+
- **Always** use LiveView streams for collections for assigning regular lists to avoid memory ballooning and runtime termination with the following operations:
225
+
- basic append of N items - `stream(socket, :messages, [new_msg])`
226
+
- resetting stream with new items - `stream(socket, :messages, [new_msg], reset: true)` (e.g. for filtering items)
227
+
- prepend to stream - `stream(socket, :messages, [new_msg], at: -1)`
228
+
- deleting items - `stream_delete(socket, :messages, msg)`
229
+
230
+
- When using the `stream/3` interfaces in the LiveView, the LiveView template must 1) always set `phx-update="stream"` on the parent element, with a DOM id on the parent element like `id="messages"` and 2) consume the `@streams.stream_name` collection and use the id as the DOM id for each child. For a call like `stream(socket, :messages, [new_msg])` in the LiveView, the template would be:
231
+
232
+
<div id="messages" phx-update="stream">
233
+
<div :for={{id, msg} <- @streams.messages} id={id}>
234
+
{msg.text}
235
+
</div>
236
+
</div>
237
+
238
+
- LiveView streams are *not* enumerable, so you cannot use `Enum.filter/2` or `Enum.reject/2` on them. Instead, if you want to filter, prune, or refresh a list of items on the UI, you **must refetch the data and re-stream the entire stream collection, passing reset: true**:
239
+
240
+
def handle_event("filter", %{"filter" => filter}, socket) do
241
+
# re-fetch the messages based on the filter
242
+
messages = list_messages(filter)
243
+
244
+
{:noreply,
245
+
socket
246
+
|> assign(:messages_empty?, messages == [])
247
+
# reset the stream with the new messages
248
+
|> stream(:messages, messages, reset: true)}
249
+
end
250
+
251
+
- LiveView streams *do not support counting or empty states*. If you need to display a count, you must track it using a separate assign. For empty states, you can use Tailwind classes:
252
+
253
+
<div id="tasks" phx-update="stream">
254
+
<div class="hidden only:block">No tasks yet</div>
255
+
<div :for={{id, task} <- @stream.tasks} id={id}>
256
+
{task.name}
257
+
</div>
258
+
</div>
259
+
260
+
The above only works if the empty state is the only HTML block alongside the stream for-comprehension.
261
+
262
+
- When updating an assign that should change content inside any streamed item(s), you MUST re-stream the items
263
+
along with the updated assign:
264
+
265
+
def handle_event("edit_message", %{"message_id" => message_id}, socket) do
266
+
message = Chat.get_message!(message_id)
267
+
edit_form = to_form(Chat.change_message(message, %{content: message.content}))
268
+
269
+
# re-insert message so @editing_message_id toggle logic takes effect for that stream item
270
+
{:noreply,
271
+
socket
272
+
|> stream_insert(:messages, message)
273
+
|> assign(:editing_message_id, String.to_integer(message_id))
274
+
|> assign(:edit_form, edit_form)}
275
+
end
276
+
277
+
And in the template:
278
+
279
+
<div id="messages" phx-update="stream">
280
+
<div :for={{id, message} <- @streams.messages} id={id} class="flex group">
281
+
{message.username}
282
+
<%= if @editing_message_id == message.id do %>
283
+
<%!-- Edit mode --%>
284
+
<.form for={@edit_form} id="edit-form-#{message.id}" phx-submit="save_edit">
285
+
...
286
+
</.form>
287
+
<% end %>
288
+
</div>
289
+
</div>
290
+
291
+
- **Never** use the deprecated `phx-update="append"` or `phx-update="prepend"` for collections
292
+
293
+
### LiveView JavaScript interop
294
+
295
+
- Remember anytime you use `phx-hook="MyHook"` and that JS hook manages its own DOM, you **must** also set the `phx-update="ignore"` attribute
296
+
- **Always** provide an unique DOM id alongside `phx-hook` otherwise a compiler error will be raised
297
+
298
+
LiveView hooks come in two flavors, 1) colocated js hooks for "inline" scripts defined inside HEEx,
299
+
and 2) external `phx-hook` annotations where JavaScript object literals are defined and passed to the `LiveSocket` constructor.
300
+
301
+
#### Inline colocated js hooks
302
+
303
+
**Never** write raw embedded `<script>` tags in heex as they are incompatible with LiveView.
304
+
Instead, **always use a colocated js hook script tag (`:type={Phoenix.LiveView.ColocatedHook}`)
305
+
when writing scripts inside the template**:
306
+
307
+
<input type="text" name="user[phone_number]" id="user-phone-number" phx-hook=".PhoneNumber" />
308
+
<script :type={Phoenix.LiveView.ColocatedHook} name=".PhoneNumber">
309
+
export default {
310
+
mounted() {
311
+
this.el.addEventListener("input", e => {
312
+
let match = this.el.value.replace(/\D/g, "").match(/^(\d{3})(\d{3})(\d{4})$/)
313
+
if(match) {
314
+
this.el.value = `${match[1]}-${match[2]}-${match[3]}`
315
+
}
316
+
})
317
+
}
318
+
}
319
+
</script>
320
+
321
+
- colocated hooks are automatically integrated into the app.js bundle
322
+
- colocated hooks names **MUST ALWAYS** start with a `.` prefix, i.e. `.PhoneNumber`
323
+
324
+
#### External phx-hook
325
+
326
+
External JS hooks (`<div id="myhook" phx-hook="MyHook">`) must be placed in `assets/js/` and passed to the
327
+
LiveSocket constructor:
328
+
329
+
const MyHook = {
330
+
mounted() { ... }
331
+
}
332
+
let liveSocket = new LiveSocket("/live", Socket, {
333
+
hooks: { MyHook }
334
+
});
335
+
336
+
#### Pushing events between client and server
337
+
338
+
Use LiveView's `push_event/3` when you need to push events/data to the client for a phx-hook to handle.
339
+
**Always** return or rebind the socket on `push_event/3` when pushing events:
340
+
341
+
# re-bind socket so we maintain event state to be pushed
342
+
socket = push_event(socket, "my_event", %{...})
343
+
344
+
# or return the modified socket directly:
345
+
def handle_event("some_event", _, socket) do
346
+
{:noreply, push_event(socket, "my_event", %{...})}
347
+
end
348
+
349
+
Pushed events can then be picked up in a JS hook with `this.handleEvent`:
350
+
351
+
mounted() {
352
+
this.handleEvent("my_event", data => console.log("from server:", data));
353
+
}
354
+
355
+
Clients can also push an event to the server and receive a reply with `this.pushEvent`:
356
+
357
+
mounted() {
358
+
this.el.addEventListener("click", e => {
359
+
this.pushEvent("my_event", { one: 1 }, reply => console.log("got reply from server:", reply));
360
+
})
361
+
}
362
+
363
+
Where the server handled it via:
364
+
365
+
def handle_event("my_event", %{"one" => 1}, socket) do
366
+
{:reply, %{two: 2}, socket}
367
+
end
368
+
369
+
### LiveView tests
370
+
371
+
- `Phoenix.LiveViewTest` module and `LazyHTML` (included) for making your assertions
372
+
- Form tests are driven by `Phoenix.LiveViewTest`'s `render_submit/2` and `render_change/2` functions
373
+
- Come up with a step-by-step test plan that splits major test cases into small, isolated files. You may start with simpler tests that verify content exists, gradually add interaction tests
374
+
- **Always reference the key element IDs you added in the LiveView templates in your tests** for `Phoenix.LiveViewTest` functions like `element/2`, `has_element/2`, selectors, etc
375
+
- **Never** tests again raw HTML, **always** use `element/2`, `has_element/2`, and similar: `assert has_element?(view, "#my-form")`
376
+
- Instead of relying on testing text content, which can change, favor testing for the presence of key elements
377
+
- Focus on testing outcomes rather than implementation details
378
+
- Be aware that `Phoenix.Component` functions like `<.form>` might produce different HTML than expected. Test against the output HTML structure, not your mental model of what you expect it to be
379
+
- When facing test failures with element selectors, add debug statements to print the actual HTML, but use `LazyHTML` selectors to limit the output, ie:
380
+
381
+
html = render(view)
382
+
document = LazyHTML.from_fragment(html)
383
+
matches = LazyHTML.filter(document, "your-complex-selector")
384
+
IO.inspect(matches, label: "Matches")
385
+
386
+
### Form handling
387
+
388
+
#### Creating a form from params
389
+
390
+
If you want to create a form based on `handle_event` params:
391
+
392
+
def handle_event("submitted", params, socket) do
393
+
{:noreply, assign(socket, form: to_form(params))}
394
+
end
395
+
396
+
When you pass a map to `to_form/1`, it assumes said map contains the form params, which are expected to have string keys.
397
+
398
+
You can also specify a name to nest the params:
399
+
400
+
def handle_event("submitted", %{"user" => user_params}, socket) do
401
+
{:noreply, assign(socket, form: to_form(user_params, as: :user))}
402
+
end
403
+
404
+
#### Creating a form from changesets
405
+
406
+
When using changesets, the underlying data, form params, and errors are retrieved from it. The `:as` option is automatically computed too. E.g. if you have a user schema:
407
+
408
+
defmodule MyApp.Users.User do
409
+
use Ecto.Schema
410
+
...
411
+
end
412
+
413
+
And then you create a changeset that you pass to `to_form`:
414
+
415
+
%MyApp.Users.User{}
416
+
|> Ecto.Changeset.change()
417
+
|> to_form()
418
+
419
+
Once the form is submitted, the params will be available under `%{"user" => user_params}`.
420
+
421
+
In the template, the form form assign can be passed to the `<.form>` function component:
422
+
423
+
<.form for={@form} id="todo-form" phx-change="validate" phx-submit="save">
424
+
<.input field={@form[:field]} type="text" />
425
+
</.form>
426
+
427
+
Always give the form an explicit, unique DOM ID, like `id="todo-form"`.
428
+
429
+
#### Avoiding form errors
430
+
431
+
**Always** use a form assigned via `to_form/2` in the LiveView, and the `<.input>` component in the template. In the template **always access forms this**:
432
+
433
+
<%!-- ALWAYS do this (valid) --%>
434
+
<.form for={@form} id="my-form">
435
+
<.input field={@form[:field]} type="text" />
436
+
</.form>
437
+
438
+
And **never** do this:
439
+
440
+
<%!-- NEVER do this (invalid) --%>
441
+
<.form for={@changeset} id="my-form">
442
+
<.input field={@changeset[:field]} type="text" />
443
+
</.form>
444
+
445
+
- You are FORBIDDEN from accessing the changeset in the template as it will cause errors
446
+
- **Never** use `<.form let={f} ...>` in the template, instead **always use `<.form for={@form} ...>`**, then drive all form references from the form assign as in `@form[:field]`. The UI should **always** be driven by a `to_form/2` assigned in the LiveView module that is derived from a changeset
447
+
<!-- phoenix:liveview-end -->
448
+
449
+
<!-- usage-rules-end -->
+4
-4
README.md
+4
-4
README.md
···
5
5
Comet's goal is to provide an open, decentralised alternative for uploading and
6
6
sharing your music with others.
7
7
8
-
Follow along on Bluesky! [@comet.sh](https://bsky.app/profile/comet.sh) /
9
-
[@ovyerus.com](https://bsky.app/profile/ovyerus.com)
8
+
Follow on Bluesky: [@comet.sh](https://bsky.app/profile/comet.sh) /
9
+
[@ovyerus.com](https://bsky.app/profile/ovyerus.com)
10
+
Or chat along on our Discord: https://discord.gg/ZKK7DnubD9
10
11
11
12
This project is in the _very_ early stages and a lot will change over time,
12
13
especially as this is my first foray into the ATProtocol ecosystem. Stuff will
···
17
18
- [ ] Experimental Lexicons
18
19
- [x] Records
19
20
- [ ] Queries
20
-
- [ ] PoC Node/Bun AppView (eventually I want to rewrite in something like Rust
21
-
or Elixir. TBD)
21
+
- [ ] AppView (Elixir)
22
22
- [ ] Clients
-481
apps/frontend/.svelte-kit/ambient.d.ts
-481
apps/frontend/.svelte-kit/ambient.d.ts
···
1
-
// this file is generated โ do not edit it
2
-
3
-
/// <reference types="@sveltejs/kit" />
4
-
5
-
/**
6
-
* Environment variables [loaded by Vite](https://vitejs.dev/guide/env-and-mode.html#env-files) from `.env` files and `process.env`. Like [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private), this module cannot be imported into client-side code. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://svelte.dev/docs/kit/configuration#env) (if configured).
7
-
*
8
-
* _Unlike_ [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private), the values exported from this module are statically injected into your bundle at build time, enabling optimisations like dead code elimination.
9
-
*
10
-
* ```ts
11
-
* import { API_KEY } from '$env/static/private';
12
-
* ```
13
-
*
14
-
* Note that all environment variables referenced in your code should be declared (for example in an `.env` file), even if they don't have a value until the app is deployed:
15
-
*
16
-
* ```
17
-
* MY_FEATURE_FLAG=""
18
-
* ```
19
-
*
20
-
* You can override `.env` values from the command line like so:
21
-
*
22
-
* ```bash
23
-
* MY_FEATURE_FLAG="enabled" npm run dev
24
-
* ```
25
-
*/
26
-
declare module "$env/static/private" {
27
-
export const SHELL: string;
28
-
export const SESSION_MANAGER: string;
29
-
export const __ETC_PROFILE_DONE: string;
30
-
export const COLORTERM: string;
31
-
export const __HM_SESS_VARS_SOURCED: string;
32
-
export const XDG_CONFIG_DIRS: string;
33
-
export const XDG_SESSION_PATH: string;
34
-
export const NIX_BUILD_CORES: string;
35
-
export const XDG_MENU_PREFIX: string;
36
-
export const TERM_PROGRAM_VERSION: string;
37
-
export const configureFlags: string;
38
-
export const SPEECHD_CMD: string;
39
-
export const VSCODE_INSPECTOR_OPTIONS: string;
40
-
export const mesonFlags: string;
41
-
export const _fifc_comp_count: string;
42
-
export const ICEAUTHORITY: string;
43
-
export const FONTCONFIG_PATH: string;
44
-
export const LANGUAGE: string;
45
-
export const shell: string;
46
-
export const depsHostHost: string;
47
-
export const NODE: string;
48
-
export const NODE_OPTIONS: string;
49
-
export const LC_ADDRESS: string;
50
-
export const _fifc_ordered_comp: string;
51
-
export const LC_NAME: string;
52
-
export const DIRENV_DIR: string;
53
-
export const STRINGS: string;
54
-
export const depsTargetTarget: string;
55
-
export const XCURSOR_PATH: string;
56
-
export const MEMORY_PRESSURE_WRITE: string;
57
-
export const stdenv: string;
58
-
export const LOCALE_ARCHIVE_2_27: string;
59
-
export const npm_config_local_prefix: string;
60
-
export const builder: string;
61
-
export const DESKTOP_SESSION: string;
62
-
export const LC_MONETARY: string;
63
-
export const GTK_RC_FILES: string;
64
-
export const NO_AT_BRIDGE: string;
65
-
export const shellHook: string;
66
-
export const DIRENV_FILE: string;
67
-
export const EDITOR: string;
68
-
export const phases: string;
69
-
export const XDG_SEAT: string;
70
-
export const PWD: string;
71
-
export const NIX_PROFILES: string;
72
-
export const SOURCE_DATE_EPOCH: string;
73
-
export const XDG_SESSION_DESKTOP: string;
74
-
export const LOGNAME: string;
75
-
export const QT_QPA_PLATFORMTHEME: string;
76
-
export const XDG_SESSION_TYPE: string;
77
-
export const NIX_ENFORCE_NO_NATIVE: string;
78
-
export const CUPS_DATADIR: string;
79
-
export const NIX_PATH: string;
80
-
export const BUN_WHICH_IGNORE_CWD: string;
81
-
export const SYSTEMD_EXEC_PID: string;
82
-
export const NIXPKGS_CONFIG: string;
83
-
export const CXX: string;
84
-
export const XAUTHORITY: string;
85
-
export const system: string;
86
-
export const FZF_DEFAULT_COMMAND: string;
87
-
export const VSCODE_GIT_ASKPASS_NODE: string;
88
-
export const HOST_PATH: string;
89
-
export const QT_STYLE_OVERRIDE: string;
90
-
export const VSCODE_INJECTION: string;
91
-
export const XKB_DEFAULT_MODEL: string;
92
-
export const GTK2_RC_FILES: string;
93
-
export const IN_NIX_SHELL: string;
94
-
export const NIXPKGS_QT6_QML_IMPORT_PATH: string;
95
-
export const doInstallCheck: string;
96
-
export const HOME: string;
97
-
export const NIX_BINTOOLS: string;
98
-
export const SSH_ASKPASS: string;
99
-
export const LANG: string;
100
-
export const LC_PAPER: string;
101
-
export const XDG_CURRENT_DESKTOP: string;
102
-
export const depsTargetTargetPropagated: string;
103
-
export const npm_package_version: string;
104
-
export const MEMORY_PRESSURE_WATCH: string;
105
-
export const WAYLAND_DISPLAY: string;
106
-
export const cmakeFlags: string;
107
-
export const outputs: string;
108
-
export const GIO_EXTRA_MODULES: string;
109
-
export const MANROFFOPT: string;
110
-
export const NIX_STORE: string;
111
-
export const GIT_ASKPASS: string;
112
-
export const XDG_SEAT_PATH: string;
113
-
export const LD: string;
114
-
export const INVOCATION_ID: string;
115
-
export const buildPhase: string;
116
-
export const MOAR: string;
117
-
export const MANGOHUD_CONFIGFILE: string;
118
-
export const MANAGERPID: string;
119
-
export const DIRENV_DIFF: string;
120
-
export const INIT_CWD: string;
121
-
export const READELF: string;
122
-
export const GTK_A11Y: string;
123
-
export const CHROME_DESKTOP: string;
124
-
export const KDE_SESSION_UID: string;
125
-
export const NIX_USER_PROFILE_DIR: string;
126
-
export const INFOPATH: string;
127
-
export const doCheck: string;
128
-
export const VSCODE_GIT_ASKPASS_EXTRA_ARGS: string;
129
-
export const XKB_DEFAULT_LAYOUT: string;
130
-
export const depsBuildBuild: string;
131
-
export const XDG_SESSION_CLASS: string;
132
-
export const _fifc_unordered_comp: string;
133
-
export const LC_IDENTIFICATION: string;
134
-
export const TERM: string;
135
-
export const npm_package_name: string;
136
-
export const FZF_CTRL_T_COMMAND: string;
137
-
export const GTK_PATH: string;
138
-
export const SIZE: string;
139
-
export const propagatedNativeBuildInputs: string;
140
-
export const strictDeps: string;
141
-
export const USER: string;
142
-
export const VSCODE_GIT_IPC_HANDLE: string;
143
-
export const TZDIR: string;
144
-
export const QT_WAYLAND_RECONNECT: string;
145
-
export const AR: string;
146
-
export const KDE_SESSION_VERSION: string;
147
-
export const AS: string;
148
-
export const MANPAGER: string;
149
-
export const DISPLAY: string;
150
-
export const NIX_BINTOOLS_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu: string;
151
-
export const _fifc_comp_1: string;
152
-
export const _fifc_comp_3: string;
153
-
export const _fifc_comp_2: string;
154
-
export const _fifc_comp_5: string;
155
-
export const _fifc_comp_4: string;
156
-
export const SHLVL: string;
157
-
export const _fifc_comp_7: string;
158
-
export const _fifc_comp_6: string;
159
-
export const _fifc_comp_9: string;
160
-
export const _fifc_comp_8: string;
161
-
export const MOZ_ENABLE_WAYLAND: string;
162
-
export const NM: string;
163
-
export const PAGER: string;
164
-
export const NIX_CFLAGS_COMPILE: string;
165
-
export const LC_TELEPHONE: string;
166
-
export const QTWEBKIT_PLUGIN_PATH: string;
167
-
export const patches: string;
168
-
export const LC_MEASUREMENT: string;
169
-
export const __NIXOS_SET_ENVIRONMENT_DONE: string;
170
-
export const XDG_VTNR: string;
171
-
export const buildInputs: string;
172
-
export const XDG_SESSION_ID: string;
173
-
export const preferLocalBuild: string;
174
-
export const LOCALE_ARCHIVE: string;
175
-
export const LESSKEYIN_SYSTEM: string;
176
-
export const npm_config_user_agent: string;
177
-
export const QML2_IMPORT_PATH: string;
178
-
export const TERMINFO_DIRS: string;
179
-
export const npm_execpath: string;
180
-
export const LD_LIBRARY_PATH: string;
181
-
export const XDG_RUNTIME_DIR: string;
182
-
export const NODE_PATH: string;
183
-
export const depsBuildTarget: string;
184
-
export const OBJCOPY: string;
185
-
export const NIX_XDG_DESKTOP_PORTAL_DIR: string;
186
-
export const out: string;
187
-
export const npm_package_json: string;
188
-
export const LC_TIME: string;
189
-
export const DOCKER_HOST: string;
190
-
export const FONTCONFIG_FILE: string;
191
-
export const VSCODE_GIT_ASKPASS_MAIN: string;
192
-
export const STRIP: string;
193
-
export const QT_AUTO_SCREEN_SCALE_FACTOR: string;
194
-
export const JOURNAL_STREAM: string;
195
-
export const XDG_DATA_DIRS: string;
196
-
export const KDE_FULL_SESSION: string;
197
-
export const GDK_BACKEND: string;
198
-
export const LIBEXEC_PATH: string;
199
-
export const OBJDUMP: string;
200
-
export const PATH: string;
201
-
export const propagatedBuildInputs: string;
202
-
export const dontAddDisableDepTrack: string;
203
-
export const __fish_nixos_env_preinit_sourced: string;
204
-
export const CC: string;
205
-
export const NIX_CC: string;
206
-
export const ORIGINAL_XDG_CURRENT_DESKTOP: string;
207
-
export const DBUS_SESSION_BUS_ADDRESS: string;
208
-
export const depsBuildTargetPropagated: string;
209
-
export const depsBuildBuildPropagated: string;
210
-
export const FZF_DEFAULT_OPTS: string;
211
-
export const DIRENV_WATCHES: string;
212
-
export const KDE_APPLICATIONS_AS_SCOPE: string;
213
-
export const NIX_CC_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu: string;
214
-
export const KPACKAGE_DEP_RESOLVERS_PATH: string;
215
-
export const QT_PLUGIN_PATH: string;
216
-
export const CONFIG_SHELL: string;
217
-
export const XKB_DEFAULT_OPTIONS: string;
218
-
export const __structuredAttrs: string;
219
-
export const npm_node_execpath: string;
220
-
export const RANLIB: string;
221
-
export const NIX_HARDENING_ENABLE: string;
222
-
export const LC_NUMERIC: string;
223
-
export const NIX_LDFLAGS: string;
224
-
export const nativeBuildInputs: string;
225
-
export const name: string;
226
-
export const TERM_PROGRAM: string;
227
-
export const depsHostHostPropagated: string;
228
-
export const _: string;
229
-
}
230
-
231
-
/**
232
-
* Similar to [`$env/static/private`](https://svelte.dev/docs/kit/$env-static-private), except that it only includes environment variables that begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) (which defaults to `PUBLIC_`), and can therefore safely be exposed to client-side code.
233
-
*
234
-
* Values are replaced statically at build time.
235
-
*
236
-
* ```ts
237
-
* import { PUBLIC_BASE_URL } from '$env/static/public';
238
-
* ```
239
-
*/
240
-
declare module "$env/static/public" {}
241
-
242
-
/**
243
-
* This module provides access to runtime environment variables, as defined by the platform you're running on. For example if you're using [`adapter-node`](https://github.com/sveltejs/kit/tree/main/packages/adapter-node) (or running [`vite preview`](https://svelte.dev/docs/kit/cli)), this is equivalent to `process.env`. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://svelte.dev/docs/kit/configuration#env) (if configured).
244
-
*
245
-
* This module cannot be imported into client-side code.
246
-
*
247
-
* Dynamic environment variables cannot be used during prerendering.
248
-
*
249
-
* ```ts
250
-
* import { env } from '$env/dynamic/private';
251
-
* console.log(env.DEPLOYMENT_SPECIFIC_VARIABLE);
252
-
* ```
253
-
*
254
-
* > In `dev`, `$env/dynamic` always includes environment variables from `.env`. In `prod`, this behavior will depend on your adapter.
255
-
*/
256
-
declare module "$env/dynamic/private" {
257
-
export const env: {
258
-
SHELL: string;
259
-
SESSION_MANAGER: string;
260
-
__ETC_PROFILE_DONE: string;
261
-
COLORTERM: string;
262
-
__HM_SESS_VARS_SOURCED: string;
263
-
XDG_CONFIG_DIRS: string;
264
-
XDG_SESSION_PATH: string;
265
-
NIX_BUILD_CORES: string;
266
-
XDG_MENU_PREFIX: string;
267
-
TERM_PROGRAM_VERSION: string;
268
-
configureFlags: string;
269
-
SPEECHD_CMD: string;
270
-
VSCODE_INSPECTOR_OPTIONS: string;
271
-
mesonFlags: string;
272
-
_fifc_comp_count: string;
273
-
ICEAUTHORITY: string;
274
-
FONTCONFIG_PATH: string;
275
-
LANGUAGE: string;
276
-
shell: string;
277
-
depsHostHost: string;
278
-
NODE: string;
279
-
NODE_OPTIONS: string;
280
-
LC_ADDRESS: string;
281
-
_fifc_ordered_comp: string;
282
-
LC_NAME: string;
283
-
DIRENV_DIR: string;
284
-
STRINGS: string;
285
-
depsTargetTarget: string;
286
-
XCURSOR_PATH: string;
287
-
MEMORY_PRESSURE_WRITE: string;
288
-
stdenv: string;
289
-
LOCALE_ARCHIVE_2_27: string;
290
-
npm_config_local_prefix: string;
291
-
builder: string;
292
-
DESKTOP_SESSION: string;
293
-
LC_MONETARY: string;
294
-
GTK_RC_FILES: string;
295
-
NO_AT_BRIDGE: string;
296
-
shellHook: string;
297
-
DIRENV_FILE: string;
298
-
EDITOR: string;
299
-
phases: string;
300
-
XDG_SEAT: string;
301
-
PWD: string;
302
-
NIX_PROFILES: string;
303
-
SOURCE_DATE_EPOCH: string;
304
-
XDG_SESSION_DESKTOP: string;
305
-
LOGNAME: string;
306
-
QT_QPA_PLATFORMTHEME: string;
307
-
XDG_SESSION_TYPE: string;
308
-
NIX_ENFORCE_NO_NATIVE: string;
309
-
CUPS_DATADIR: string;
310
-
NIX_PATH: string;
311
-
BUN_WHICH_IGNORE_CWD: string;
312
-
SYSTEMD_EXEC_PID: string;
313
-
NIXPKGS_CONFIG: string;
314
-
CXX: string;
315
-
XAUTHORITY: string;
316
-
system: string;
317
-
FZF_DEFAULT_COMMAND: string;
318
-
VSCODE_GIT_ASKPASS_NODE: string;
319
-
HOST_PATH: string;
320
-
QT_STYLE_OVERRIDE: string;
321
-
VSCODE_INJECTION: string;
322
-
XKB_DEFAULT_MODEL: string;
323
-
GTK2_RC_FILES: string;
324
-
IN_NIX_SHELL: string;
325
-
NIXPKGS_QT6_QML_IMPORT_PATH: string;
326
-
doInstallCheck: string;
327
-
HOME: string;
328
-
NIX_BINTOOLS: string;
329
-
SSH_ASKPASS: string;
330
-
LANG: string;
331
-
LC_PAPER: string;
332
-
XDG_CURRENT_DESKTOP: string;
333
-
depsTargetTargetPropagated: string;
334
-
npm_package_version: string;
335
-
MEMORY_PRESSURE_WATCH: string;
336
-
WAYLAND_DISPLAY: string;
337
-
cmakeFlags: string;
338
-
outputs: string;
339
-
GIO_EXTRA_MODULES: string;
340
-
MANROFFOPT: string;
341
-
NIX_STORE: string;
342
-
GIT_ASKPASS: string;
343
-
XDG_SEAT_PATH: string;
344
-
LD: string;
345
-
INVOCATION_ID: string;
346
-
buildPhase: string;
347
-
MOAR: string;
348
-
MANGOHUD_CONFIGFILE: string;
349
-
MANAGERPID: string;
350
-
DIRENV_DIFF: string;
351
-
INIT_CWD: string;
352
-
READELF: string;
353
-
GTK_A11Y: string;
354
-
CHROME_DESKTOP: string;
355
-
KDE_SESSION_UID: string;
356
-
NIX_USER_PROFILE_DIR: string;
357
-
INFOPATH: string;
358
-
doCheck: string;
359
-
VSCODE_GIT_ASKPASS_EXTRA_ARGS: string;
360
-
XKB_DEFAULT_LAYOUT: string;
361
-
depsBuildBuild: string;
362
-
XDG_SESSION_CLASS: string;
363
-
_fifc_unordered_comp: string;
364
-
LC_IDENTIFICATION: string;
365
-
TERM: string;
366
-
npm_package_name: string;
367
-
FZF_CTRL_T_COMMAND: string;
368
-
GTK_PATH: string;
369
-
SIZE: string;
370
-
propagatedNativeBuildInputs: string;
371
-
strictDeps: string;
372
-
USER: string;
373
-
VSCODE_GIT_IPC_HANDLE: string;
374
-
TZDIR: string;
375
-
QT_WAYLAND_RECONNECT: string;
376
-
AR: string;
377
-
KDE_SESSION_VERSION: string;
378
-
AS: string;
379
-
MANPAGER: string;
380
-
DISPLAY: string;
381
-
NIX_BINTOOLS_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu: string;
382
-
_fifc_comp_1: string;
383
-
_fifc_comp_3: string;
384
-
_fifc_comp_2: string;
385
-
_fifc_comp_5: string;
386
-
_fifc_comp_4: string;
387
-
SHLVL: string;
388
-
_fifc_comp_7: string;
389
-
_fifc_comp_6: string;
390
-
_fifc_comp_9: string;
391
-
_fifc_comp_8: string;
392
-
MOZ_ENABLE_WAYLAND: string;
393
-
NM: string;
394
-
PAGER: string;
395
-
NIX_CFLAGS_COMPILE: string;
396
-
LC_TELEPHONE: string;
397
-
QTWEBKIT_PLUGIN_PATH: string;
398
-
patches: string;
399
-
LC_MEASUREMENT: string;
400
-
__NIXOS_SET_ENVIRONMENT_DONE: string;
401
-
XDG_VTNR: string;
402
-
buildInputs: string;
403
-
XDG_SESSION_ID: string;
404
-
preferLocalBuild: string;
405
-
LOCALE_ARCHIVE: string;
406
-
LESSKEYIN_SYSTEM: string;
407
-
npm_config_user_agent: string;
408
-
QML2_IMPORT_PATH: string;
409
-
TERMINFO_DIRS: string;
410
-
npm_execpath: string;
411
-
LD_LIBRARY_PATH: string;
412
-
XDG_RUNTIME_DIR: string;
413
-
NODE_PATH: string;
414
-
depsBuildTarget: string;
415
-
OBJCOPY: string;
416
-
NIX_XDG_DESKTOP_PORTAL_DIR: string;
417
-
out: string;
418
-
npm_package_json: string;
419
-
LC_TIME: string;
420
-
DOCKER_HOST: string;
421
-
FONTCONFIG_FILE: string;
422
-
VSCODE_GIT_ASKPASS_MAIN: string;
423
-
STRIP: string;
424
-
QT_AUTO_SCREEN_SCALE_FACTOR: string;
425
-
JOURNAL_STREAM: string;
426
-
XDG_DATA_DIRS: string;
427
-
KDE_FULL_SESSION: string;
428
-
GDK_BACKEND: string;
429
-
LIBEXEC_PATH: string;
430
-
OBJDUMP: string;
431
-
PATH: string;
432
-
propagatedBuildInputs: string;
433
-
dontAddDisableDepTrack: string;
434
-
__fish_nixos_env_preinit_sourced: string;
435
-
CC: string;
436
-
NIX_CC: string;
437
-
ORIGINAL_XDG_CURRENT_DESKTOP: string;
438
-
DBUS_SESSION_BUS_ADDRESS: string;
439
-
depsBuildTargetPropagated: string;
440
-
depsBuildBuildPropagated: string;
441
-
FZF_DEFAULT_OPTS: string;
442
-
DIRENV_WATCHES: string;
443
-
KDE_APPLICATIONS_AS_SCOPE: string;
444
-
NIX_CC_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu: string;
445
-
KPACKAGE_DEP_RESOLVERS_PATH: string;
446
-
QT_PLUGIN_PATH: string;
447
-
CONFIG_SHELL: string;
448
-
XKB_DEFAULT_OPTIONS: string;
449
-
__structuredAttrs: string;
450
-
npm_node_execpath: string;
451
-
RANLIB: string;
452
-
NIX_HARDENING_ENABLE: string;
453
-
LC_NUMERIC: string;
454
-
NIX_LDFLAGS: string;
455
-
nativeBuildInputs: string;
456
-
name: string;
457
-
TERM_PROGRAM: string;
458
-
depsHostHostPropagated: string;
459
-
_: string;
460
-
[key: `PUBLIC_${string}`]: undefined;
461
-
[key: `${string}`]: string | undefined;
462
-
};
463
-
}
464
-
465
-
/**
466
-
* Similar to [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private), but only includes variables that begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) (which defaults to `PUBLIC_`), and can therefore safely be exposed to client-side code.
467
-
*
468
-
* Note that public dynamic environment variables must all be sent from the server to the client, causing larger network requests โ when possible, use `$env/static/public` instead.
469
-
*
470
-
* Dynamic environment variables cannot be used during prerendering.
471
-
*
472
-
* ```ts
473
-
* import { env } from '$env/dynamic/public';
474
-
* console.log(env.PUBLIC_DEPLOYMENT_SPECIFIC_VARIABLE);
475
-
* ```
476
-
*/
477
-
declare module "$env/dynamic/public" {
478
-
export const env: {
479
-
[key: `PUBLIC_${string}`]: string | undefined;
480
-
};
481
-
}
-30
apps/frontend/.svelte-kit/non-ambient.d.ts
-30
apps/frontend/.svelte-kit/non-ambient.d.ts
···
1
-
// this file is generated โ do not edit it
2
-
3
-
declare module "svelte/elements" {
4
-
export interface HTMLAttributes<T> {
5
-
"data-sveltekit-keepfocus"?: true | "" | "off" | undefined | null;
6
-
"data-sveltekit-noscroll"?: true | "" | "off" | undefined | null;
7
-
"data-sveltekit-preload-code"?:
8
-
| true
9
-
| ""
10
-
| "eager"
11
-
| "viewport"
12
-
| "hover"
13
-
| "tap"
14
-
| "off"
15
-
| undefined
16
-
| null;
17
-
"data-sveltekit-preload-data"?:
18
-
| true
19
-
| ""
20
-
| "hover"
21
-
| "tap"
22
-
| "off"
23
-
| undefined
24
-
| null;
25
-
"data-sveltekit-reload"?: true | "" | "off" | undefined | null;
26
-
"data-sveltekit-replacestate"?: true | "" | "off" | undefined | null;
27
-
}
28
-
}
29
-
30
-
export {};
-38
apps/frontend/.svelte-kit/tsconfig.json
-38
apps/frontend/.svelte-kit/tsconfig.json
···
1
-
{
2
-
"compilerOptions": {
3
-
"paths": {
4
-
"$lib": ["../src/lib"],
5
-
"$lib/*": ["../src/lib/*"]
6
-
},
7
-
"rootDirs": ["..", "./types"],
8
-
"verbatimModuleSyntax": true,
9
-
"isolatedModules": true,
10
-
"lib": ["esnext", "DOM", "DOM.Iterable"],
11
-
"moduleResolution": "bundler",
12
-
"module": "esnext",
13
-
"noEmit": true,
14
-
"target": "esnext"
15
-
},
16
-
"include": [
17
-
"ambient.d.ts",
18
-
"non-ambient.d.ts",
19
-
"./types/**/$types.d.ts",
20
-
"../vite.config.js",
21
-
"../vite.config.ts",
22
-
"../src/**/*.js",
23
-
"../src/**/*.ts",
24
-
"../src/**/*.svelte",
25
-
"../tests/**/*.js",
26
-
"../tests/**/*.ts",
27
-
"../tests/**/*.svelte"
28
-
],
29
-
"exclude": [
30
-
"../node_modules/**",
31
-
"../src/service-worker.js",
32
-
"../src/service-worker/**/*.js",
33
-
"../src/service-worker.ts",
34
-
"../src/service-worker/**/*.ts",
35
-
"../src/service-worker.d.ts",
36
-
"../src/service-worker/**/*.d.ts"
37
-
]
38
-
}
-40
apps/frontend/.svelte-kit/types/src/routes/$types.d.ts
-40
apps/frontend/.svelte-kit/types/src/routes/$types.d.ts
···
1
-
import type * as Kit from "@sveltejs/kit";
2
-
3
-
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
4
-
// @ts-ignore
5
-
type MatcherParam<M> = M extends (param: string) => param is infer U
6
-
? U extends string
7
-
? U
8
-
: string
9
-
: string;
10
-
type RouteParams = {};
11
-
type RouteId = "/";
12
-
type MaybeWithVoid<T> = {} extends T ? T | void : T;
13
-
export type RequiredKeys<T> = {
14
-
[K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K;
15
-
}[keyof T];
16
-
type OutputDataShape<T> = MaybeWithVoid<
17
-
Omit<App.PageData, RequiredKeys<T>> &
18
-
Partial<Pick<App.PageData, keyof T & keyof App.PageData>> &
19
-
Record<string, any>
20
-
>;
21
-
type EnsureDefined<T> = T extends null | undefined ? {} : T;
22
-
type OptionalUnion<
23
-
U extends Record<string, any>,
24
-
A extends keyof U = U extends U ? keyof U : never,
25
-
> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
26
-
export type Snapshot<T = any> = Kit.Snapshot<T>;
27
-
type PageParentData = EnsureDefined<LayoutData>;
28
-
type LayoutRouteId = RouteId | "/" | null;
29
-
type LayoutParams = RouteParams & {};
30
-
type LayoutParentData = EnsureDefined<{}>;
31
-
32
-
export type PageServerData = null;
33
-
export type PageData = Expand<PageParentData>;
34
-
export type PageProps = { data: PageData };
35
-
export type LayoutServerData = null;
36
-
export type LayoutData = Expand<LayoutParentData>;
37
-
export type LayoutProps = {
38
-
data: LayoutData;
39
-
children: import("svelte").Snippet;
40
-
};
-37
apps/frontend/eslint.config.js
-37
apps/frontend/eslint.config.js
···
1
-
import prettier from "eslint-config-prettier";
2
-
import js from "@eslint/js";
3
-
import { includeIgnoreFile } from "@eslint/compat";
4
-
import svelte from "eslint-plugin-svelte";
5
-
import globals from "globals";
6
-
import { fileURLToPath } from "node:url";
7
-
import ts from "typescript-eslint";
8
-
import svelteConfig from "./svelte.config.js";
9
-
10
-
const gitignorePath = fileURLToPath(new URL("./.gitignore", import.meta.url));
11
-
12
-
export default ts.config(
13
-
includeIgnoreFile(gitignorePath),
14
-
js.configs.recommended,
15
-
...ts.configs.recommended,
16
-
...svelte.configs.recommended,
17
-
prettier,
18
-
...svelte.configs.prettier,
19
-
{
20
-
languageOptions: {
21
-
globals: { ...globals.browser, ...globals.node },
22
-
},
23
-
rules: { "no-undef": "off" },
24
-
},
25
-
{
26
-
files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"],
27
-
ignores: ["eslint.config.js", "svelte.config.js"],
28
-
languageOptions: {
29
-
parserOptions: {
30
-
projectService: true,
31
-
extraFileExtensions: [".svelte"],
32
-
parser: ts.parser,
33
-
svelteConfig,
34
-
},
35
-
},
36
-
},
37
-
);
-36
apps/frontend/package.json
-36
apps/frontend/package.json
···
1
-
{
2
-
"name": "comet",
3
-
"private": true,
4
-
"version": "0.0.1",
5
-
"type": "module",
6
-
"scripts": {
7
-
"dev": "vite dev",
8
-
"build": "vite build",
9
-
"preview": "vite preview",
10
-
"prepare": "svelte-kit sync || echo ''",
11
-
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
12
-
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
13
-
},
14
-
"devDependencies": {
15
-
"@eslint/compat": "^1.2.5",
16
-
"@eslint/js": "^9.18.0",
17
-
"@fontsource-variable/inter": "^5.2.5",
18
-
"@lucide/svelte": "^0.487.0",
19
-
"@sveltejs/adapter-auto": "^4.0.0",
20
-
"@sveltejs/kit": "^2.16.0",
21
-
"@sveltejs/vite-plugin-svelte": "^5.0.0",
22
-
"@tailwindcss/vite": "^4.0.0",
23
-
"bits-ui": "^1.3.17",
24
-
"clsx": "^2.1.1",
25
-
"eslint": "^9.18.0",
26
-
"eslint-config-prettier": "^10.0.1",
27
-
"eslint-plugin-svelte": "^3.0.0",
28
-
"globals": "^16.0.0",
29
-
"svelte": "^5.0.0",
30
-
"svelte-check": "^4.0.0",
31
-
"tailwindcss": "^4.0.0",
32
-
"typescript": "^5.0.0",
33
-
"typescript-eslint": "^8.20.0",
34
-
"vite": "^6.2.5"
35
-
}
36
-
}
-13
apps/frontend/src/app.css
-13
apps/frontend/src/app.css
···
1
-
@import "tailwindcss";
2
-
@import "@fontsource-variable/inter";
3
-
4
-
@theme {
5
-
--font-sans:
6
-
"Inter Variable", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
7
-
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
8
-
--font-sans--font-feature-settings: "tnum", "ss01", "ss02";
9
-
}
10
-
11
-
body {
12
-
@apply bg-slate-100;
13
-
}
-13
apps/frontend/src/app.d.ts
-13
apps/frontend/src/app.d.ts
···
1
-
// See https://svelte.dev/docs/kit/types#app.d.ts
2
-
// for information about these interfaces
3
-
declare global {
4
-
namespace App {
5
-
// interface Error {}
6
-
// interface Locals {}
7
-
// interface PageData {}
8
-
// interface PageState {}
9
-
// interface Platform {}
10
-
}
11
-
}
12
-
13
-
export {};
-12
apps/frontend/src/app.html
-12
apps/frontend/src/app.html
···
1
-
<!doctype html>
2
-
<html lang="en">
3
-
<head>
4
-
<meta charset="utf-8" />
5
-
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
6
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
7
-
%sveltekit.head%
8
-
</head>
9
-
<body data-sveltekit-preload-data="hover">
10
-
<div style="display: contents">%sveltekit.body%</div>
11
-
</body>
12
-
</html>
-107
apps/frontend/src/lib/components/Player1.svelte
-107
apps/frontend/src/lib/components/Player1.svelte
···
1
-
<script lang="ts">
2
-
import {
3
-
List,
4
-
Pause,
5
-
Play,
6
-
Repeat,
7
-
Shuffle,
8
-
SkipBack,
9
-
SkipForward,
10
-
Volume2,
11
-
type Icon as LucideIcon,
12
-
} from "@lucide/svelte";
13
-
import { Slider } from "bits-ui";
14
-
import cn from "clsx";
15
-
16
-
let playing = $state(true);
17
-
let shuffle = $state(false);
18
-
let repeat = $state(false);
19
-
20
-
const MainIcon = $derived(playing ? Pause : Play);
21
-
22
-
const songLength = 256;
23
-
let playback = $state(0);
24
-
</script>
25
-
26
-
{#snippet plainButton(Icon: typeof LucideIcon, label: string)}
27
-
<button class="flex cursor-pointer" aria-label={label}>
28
-
<Icon />
29
-
</button>
30
-
{/snippet}
31
-
32
-
{#snippet clickable(content: string)}
33
-
<span class="cursor-pointer hover:underline">{content}</span>
34
-
{/snippet}
35
-
36
-
<!-- TODO: labelled by the artist & title -->
37
-
<aside
38
-
class="fixed right-2 bottom-2 left-2 flex items-center gap-4 rounded-lg border border-slate-300 bg-white p-2 px-4 text-slate-500"
39
-
>
40
-
<div class="flex items-center gap-2 text-slate-900">
41
-
{@render plainButton(SkipBack, "Previous song")}
42
-
<button
43
-
class="flex cursor-pointer items-center justify-center rounded-full bg-orange-500 p-2 text-white"
44
-
aria-label="Play"
45
-
onclick={() => (playing = !playing)}
46
-
>
47
-
<MainIcon />
48
-
</button>
49
-
{@render plainButton(SkipForward, "Next song")}
50
-
</div>
51
-
52
-
<div class="flex items-center gap-2">
53
-
<img
54
-
src="https://lh3.googleusercontent.com/0z6Kg2GFi8hFgZYxWm3c3UNul0gyaCQjuqmY-p1oeFC1n5EMOf1dxrownTzhzk-_cdtO_FLLktQcMecwGQ=w544-h544-l90-rj"
55
-
class="h-12 w-12 rounded object-cover object-center"
56
-
alt=""
57
-
/>
58
-
<div class="flex flex-col">
59
-
<span class="text-sm font-semibold text-slate-900 opacity-70">
60
-
{@render clickable("Protostar")}, {@render clickable("Laminar")} & {@render clickable(
61
-
"imallryt",
62
-
)}
63
-
</span>
64
-
<span class="font-bolder text-sm font-semibold text-slate-900">
65
-
{@render clickable("Blood in the Water")}
66
-
<!-- <span class="opacity-50">| {@render clickable("Epic Album")}</span> -->
67
-
</span>
68
-
</div>
69
-
</div>
70
-
71
-
<div class="flex flex-1 px-30">
72
-
<Slider.Root
73
-
type="single"
74
-
bind:value={playback}
75
-
max={songLength}
76
-
class="relative flex flex-1 touch-none items-center select-none"
77
-
>
78
-
{#snippet children()}
79
-
<span
80
-
class="relative h-1 w-full cursor-pointer overflow-hidden rounded-full bg-slate-200"
81
-
>
82
-
<Slider.Range class="absolute h-full rounded-full bg-orange-500" />
83
-
</span>
84
-
<Slider.Thumb
85
-
index={0}
86
-
class="block size-4 cursor-pointer rounded-full border border-slate-900 bg-slate-50 focus-visible:ring focus-visible:ring-orange-500 focus-visible:ring-offset-2 "
87
-
/>
88
-
{/snippet}
89
-
</Slider.Root>
90
-
</div>
91
-
92
-
<button
93
-
class={cn("flex", "cursor-pointer", { "text-orange-500": shuffle })}
94
-
onclick={() => (shuffle = !shuffle)}
95
-
>
96
-
<Shuffle />
97
-
</button>
98
-
<button
99
-
class={cn("flex", "cursor-pointer", { "text-orange-500": repeat })}
100
-
onclick={() => (repeat = !repeat)}
101
-
>
102
-
<Repeat />
103
-
</button>
104
-
105
-
<List class="cursor-pointer" />
106
-
<Volume2 class="cursor-pointer" />
107
-
</aside>
-201
apps/frontend/src/lib/components/Player2.svelte
-201
apps/frontend/src/lib/components/Player2.svelte
···
1
-
<script lang="ts">
2
-
import {
3
-
ChevronDown,
4
-
List,
5
-
Pause,
6
-
Play,
7
-
Repeat,
8
-
Repeat1,
9
-
Shuffle,
10
-
SkipBack,
11
-
SkipForward,
12
-
Volume2,
13
-
type Icon as LucideIcon,
14
-
} from "@lucide/svelte";
15
-
import { Slider } from "bits-ui";
16
-
import cn from "clsx";
17
-
18
-
let expanded = $state(true);
19
-
let playing = $state(false);
20
-
let shuffle = $state(false);
21
-
let repeat: "none" | "all" | "one" = $state("none");
22
-
let interval: ReturnType<typeof setInterval>;
23
-
24
-
const MainIcon = $derived(playing ? Pause : Play);
25
-
const RepeatIcon = $derived.by(() => (repeat === "one" ? Repeat1 : Repeat));
26
-
27
-
// TODO: separate progress state so that the thumb does not jump around to it's real value while the user is dragging it.
28
-
// Probably done through checking click state of the thumb and temporarily disconnecting the progress?
29
-
const songLength = 256;
30
-
let playback = $state(0);
31
-
32
-
$effect(() => {
33
-
// console.log({ playback });
34
-
35
-
if (playing)
36
-
interval = setInterval(() => {
37
-
playback++;
38
-
if (playback > songLength) playback = 0;
39
-
}, 1000);
40
-
else clearInterval(interval);
41
-
});
42
-
43
-
// TODO: see if I need to i18n time format.
44
-
const formatTime = (inputSeconds: number) => {
45
-
const minutes = Math.floor(inputSeconds / 60);
46
-
const seconds = `${inputSeconds % 60}`.padStart(2, "0");
47
-
48
-
return `${minutes}:${seconds}`;
49
-
};
50
-
51
-
const cycleRepeat = () => {
52
-
if (repeat === "none") repeat = "all";
53
-
else if (repeat === "all") repeat = "one";
54
-
else repeat = "none";
55
-
};
56
-
</script>
57
-
58
-
{#snippet plainButton(Icon: typeof LucideIcon, label: string)}
59
-
<button class="flex cursor-pointer" aria-label={label}>
60
-
<Icon />
61
-
</button>
62
-
{/snippet}
63
-
64
-
{#snippet clickable(content: string)}
65
-
<span class="cursor-pointer hover:underline">{content}</span>
66
-
{/snippet}
67
-
68
-
<!-- TODO: labelled by the artist & title -->
69
-
<!-- TODO: keep width when collapsed? -->
70
-
<aside
71
-
class="fixed bottom-2 left-2 flex flex-col gap-2 overflow-hidden rounded-lg border border-slate-300 bg-white"
72
-
>
73
-
<header class="flex gap-2 bg-black p-2 text-slate-50">
74
-
<button
75
-
class={cn("flex cursor-pointer transition-transform", {
76
-
"rotate-180": !expanded,
77
-
})}
78
-
onclick={() => (expanded = !expanded)}
79
-
>
80
-
<ChevronDown />
81
-
</button>
82
-
{#if expanded}
83
-
<span class="font-bold">Now Playing</span>
84
-
<div class="flex-1"></div>
85
-
86
-
<button
87
-
class={cn("flex", "cursor-pointer", { "text-orange-500": shuffle })}
88
-
onclick={() => (shuffle = !shuffle)}
89
-
>
90
-
<Shuffle />
91
-
</button>
92
-
<button
93
-
class={cn("flex", "cursor-pointer", {
94
-
"text-orange-500": repeat !== "none",
95
-
})}
96
-
onclick={cycleRepeat}
97
-
>
98
-
<RepeatIcon />
99
-
</button>
100
-
101
-
<List class="cursor-pointer" />
102
-
<Volume2 class="cursor-pointer" />
103
-
{:else}
104
-
<div class="flex gap-1">
105
-
<button
106
-
class="flex cursor-pointer"
107
-
aria-label={playing ? "Pause current song" : "Play current song"}
108
-
onclick={() => (playing = !playing)}
109
-
>
110
-
<MainIcon />
111
-
</button>
112
-
<!-- TODO: scrolling text -->
113
-
<div class="line-clamp-1 max-w-[300px] text-ellipsis">
114
-
<span>Protostar, Laminar, imallryt</span>
115
-
-
116
-
<span>Blood in the Water</span>
117
-
</div>
118
-
119
-
<Volume2 class="cursor-pointer" />
120
-
</div>
121
-
{/if}
122
-
</header>
123
-
124
-
{#if expanded}
125
-
<div class="flex flex-col gap-2 px-4 py-2 text-slate-500">
126
-
<div class="flex items-center gap-4">
127
-
<div class="flex items-center gap-2 text-slate-900">
128
-
{@render plainButton(SkipBack, "Previous song")}
129
-
<button
130
-
class="flex cursor-pointer items-center justify-center rounded-full bg-orange-500 p-2 text-white"
131
-
aria-label={playing ? "Pause current song" : "Play current song"}
132
-
onclick={() => (playing = !playing)}
133
-
>
134
-
<MainIcon />
135
-
</button>
136
-
{@render plainButton(SkipForward, "Next song")}
137
-
</div>
138
-
139
-
<div class="flex items-center gap-2">
140
-
<img
141
-
src="https://lh3.googleusercontent.com/0z6Kg2GFi8hFgZYxWm3c3UNul0gyaCQjuqmY-p1oeFC1n5EMOf1dxrownTzhzk-_cdtO_FLLktQcMecwGQ=w544-h544-l90-rj"
142
-
class="h-12 w-12 rounded object-cover object-center"
143
-
alt=""
144
-
/>
145
-
<!-- TODO: max width with scrolling texts -->
146
-
<div class="flex flex-col">
147
-
<span class="text-sm font-semibold text-slate-900 opacity-70">
148
-
{@render clickable("Protostar")}, {@render clickable("Laminar")} &
149
-
{@render clickable("imallryt")}
150
-
</span>
151
-
<span class="font-bolder text-sm font-semibold text-slate-900">
152
-
{@render clickable("Blood in the Water")}
153
-
<!-- <span class="opacity-50">| {@render clickable("Epic Album")}</span> -->
154
-
</span>
155
-
</div>
156
-
</div>
157
-
</div>
158
-
159
-
<div class="flex w-full gap-2 py-2">
160
-
<Slider.Root
161
-
type="single"
162
-
max={songLength}
163
-
class="relative flex flex-1 touch-none items-center select-none"
164
-
value={playback}
165
-
onValueCommit={(value) => (playback = value)}
166
-
>
167
-
{#snippet children()}
168
-
<span
169
-
class="relative h-1 w-full cursor-pointer overflow-hidden rounded-full bg-slate-200"
170
-
>
171
-
<Slider.Range
172
-
class="absolute h-full rounded-full bg-orange-500"
173
-
/>
174
-
</span>
175
-
<Slider.Thumb
176
-
index={0}
177
-
class="block size-4 cursor-pointer rounded-full border border-slate-900 bg-slate-50 focus-visible:ring focus-visible:ring-orange-500 focus-visible:ring-offset-2 "
178
-
/>
179
-
{/snippet}
180
-
</Slider.Root>
181
-
182
-
<span class="text-sm">
183
-
{formatTime(playback)}/{formatTime(songLength)}
184
-
</span>
185
-
186
-
<!-- <button
187
-
class={cn("flex", "cursor-pointer", { "text-orange-500": shuffle })}
188
-
onclick={() => (shuffle = !shuffle)}
189
-
>
190
-
<Shuffle />
191
-
</button>
192
-
<button
193
-
class={cn("flex", "cursor-pointer", { "text-orange-500": repeat })}
194
-
onclick={() => (repeat = !repeat)}
195
-
>
196
-
<Repeat />
197
-
</button> -->
198
-
</div>
199
-
</div>
200
-
{/if}
201
-
</aside>
-1
apps/frontend/src/lib/index.ts
-1
apps/frontend/src/lib/index.ts
···
1
-
// place files you want to import through the `$lib` alias in this folder.
-15
apps/frontend/src/routes/+layout.svelte
-15
apps/frontend/src/routes/+layout.svelte
···
1
-
<script lang="ts">
2
-
import Navbar from "$lib/components/Navbar.svelte";
3
-
import Player1 from "$lib/components/Player1.svelte";
4
-
import Player2 from "$lib/components/Player2.svelte";
5
-
import "../app.css";
6
-
7
-
let { children } = $props();
8
-
</script>
9
-
10
-
<Navbar />
11
-
<main class="m-2 flex flex-col items-center">
12
-
{@render children()}
13
-
</main>
14
-
<!-- <Player1 /> -->
15
-
<Player2 />
-32
apps/frontend/src/routes/+page.svelte
-32
apps/frontend/src/routes/+page.svelte
···
1
-
<section class="flex flex-col items-center gap-2 pt-10">
2
-
<header class="flex flex-col items-center">
3
-
<h1 class="text-5xl font-bold tracking-tighter text-orange-600">Comet</h1>
4
-
<p class="flex flex-col items-center text-center text-lg">
5
-
Your music, on ATProto.
6
-
</p>
7
-
</header>
8
-
9
-
<div>
10
-
<h2 class="text-2xl font-bold tracking-tighter text-orange-600">Why?</h2>
11
-
<ol class="list-disc">
12
-
<li>
13
-
free yourself from Big Techโข๏ธ; don't let them tell you what music you
14
-
can and can't make
15
-
</li>
16
-
<li>
17
-
Listen to music in full* quality, without being subject to horrendous
18
-
sounding data compression.
19
-
</li>
20
-
<li>
21
-
if I die, all your data is yours, and can be used by other projects!
22
-
</li>
23
-
<li>
24
-
Integrate your playback with <span class="text-teal-800 underline">
25
-
teal.fm
26
-
</span>
27
-
and let everyone else know what you're listening to!
28
-
</li>
29
-
<li>borpa</li>
30
-
</ol>
31
-
</div>
32
-
</section>
apps/frontend/static/favicon.png
apps/frontend/static/favicon.png
This is a binary file and will not be displayed.
-18
apps/frontend/svelte.config.js
-18
apps/frontend/svelte.config.js
···
1
-
import adapter from "@sveltejs/adapter-auto";
2
-
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
3
-
4
-
/** @type {import('@sveltejs/kit').Config} */
5
-
const config = {
6
-
// Consult https://svelte.dev/docs/kit/integrations
7
-
// for more information about preprocessors
8
-
preprocess: vitePreprocess(),
9
-
10
-
kit: {
11
-
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
12
-
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
13
-
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
14
-
adapter: adapter(),
15
-
},
16
-
};
17
-
18
-
export default config;
-19
apps/frontend/tsconfig.json
-19
apps/frontend/tsconfig.json
···
1
-
{
2
-
"extends": "./.svelte-kit/tsconfig.json",
3
-
"compilerOptions": {
4
-
"allowJs": true,
5
-
"checkJs": true,
6
-
"esModuleInterop": true,
7
-
"forceConsistentCasingInFileNames": true,
8
-
"resolveJsonModule": true,
9
-
"skipLibCheck": true,
10
-
"sourceMap": true,
11
-
"strict": true,
12
-
"moduleResolution": "bundler"
13
-
}
14
-
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
15
-
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
16
-
//
17
-
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
18
-
// from the referenced tsconfig.json - TypeScript does not merge them in
19
-
}
-7
apps/frontend/vite.config.ts
-7
apps/frontend/vite.config.ts
+30
assets/css/app.css
+30
assets/css/app.css
···
1
+
/* See the Tailwind configuration guide for advanced usage
2
+
https://tailwindcss.com/docs/configuration */
3
+
4
+
@import "tailwindcss" source(none);
5
+
@source "../css";
6
+
@source "../js";
7
+
@source "../../lib/comet_app";
8
+
@source "../../lib/comet_web";
9
+
10
+
/* A Tailwind plugin that makes "hero-#{ICON}" classes available.
11
+
The heroicons installation itself is managed by your mix.exs */
12
+
@plugin "../vendor/heroicons";
13
+
14
+
/* Add variants based on LiveView classes */
15
+
@custom-variant phx-click-loading (.phx-click-loading&, .phx-click-loading &);
16
+
@custom-variant phx-submit-loading (.phx-submit-loading&, .phx-submit-loading &);
17
+
@custom-variant phx-change-loading (.phx-change-loading&, .phx-change-loading &);
18
+
19
+
/* Use the data attribute for dark mode */
20
+
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
21
+
22
+
/* Make LiveView wrapper divs transparent for layout */
23
+
[data-phx-session], [data-phx-teleported-src] { display: contents }
24
+
25
+
/* This file is for your main application CSS */
26
+
@theme {
27
+
--color-brand-light: #ffa34e;
28
+
--color-brand-dark: #ff4400;
29
+
--font-sans: "Work Sans Variable", ui-sans-serif, system-ui, sans-serif;
30
+
}
+84
assets/js/app.js
+84
assets/js/app.js
···
1
+
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
2
+
// to get started and then uncomment the line below.
3
+
// import "./user_socket.js"
4
+
5
+
// You can include dependencies in two ways.
6
+
//
7
+
// The simplest option is to put them in assets/vendor and
8
+
// import them using relative paths:
9
+
//
10
+
// import "../vendor/some-package.js"
11
+
//
12
+
// Alternatively, you can `npm install some-package --prefix assets` and import
13
+
// them using a path starting with the package name:
14
+
//
15
+
// import "some-package"
16
+
//
17
+
// If you have dependencies that try to import CSS, esbuild will generate a separate `app.css` file.
18
+
// To load it, simply add a second `<link>` to your `root.html.heex` file.
19
+
20
+
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
21
+
import "phoenix_html"
22
+
import "@fontsource-variable/work-sans"
23
+
// Establish Phoenix Socket and LiveView configuration.
24
+
import {Socket} from "phoenix"
25
+
import {LiveSocket} from "phoenix_live_view"
26
+
import {hooks as colocatedHooks} from "phoenix-colocated/comet"
27
+
import topbar from "../vendor/topbar"
28
+
29
+
const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
30
+
const liveSocket = new LiveSocket("/live", Socket, {
31
+
longPollFallbackMs: 2500,
32
+
params: {_csrf_token: csrfToken},
33
+
hooks: {...colocatedHooks},
34
+
})
35
+
36
+
// Show progress bar on live navigation and form submits
37
+
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
38
+
window.addEventListener("phx:page-loading-start", _info => topbar.show(300))
39
+
window.addEventListener("phx:page-loading-stop", _info => topbar.hide())
40
+
41
+
// connect if there are any LiveViews on the page
42
+
liveSocket.connect()
43
+
44
+
// expose liveSocket on window for web console debug logs and latency simulation:
45
+
// >> liveSocket.enableDebug()
46
+
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
47
+
// >> liveSocket.disableLatencySim()
48
+
window.liveSocket = liveSocket
49
+
50
+
// The lines below enable quality of life phoenix_live_reload
51
+
// development features:
52
+
//
53
+
// 1. stream server logs to the browser console
54
+
// 2. click on elements to jump to their definitions in your code editor
55
+
//
56
+
if (process.env.NODE_ENV === "development") {
57
+
window.addEventListener("phx:live_reload:attached", ({detail: reloader}) => {
58
+
// Enable server log streaming to client.
59
+
// Disable with reloader.disableServerLogs()
60
+
reloader.enableServerLogs()
61
+
62
+
// Open configured PLUG_EDITOR at file:line of the clicked element's HEEx component
63
+
//
64
+
// * click with "c" key pressed to open at caller location
65
+
// * click with "d" key pressed to open at function component definition location
66
+
let keyDown
67
+
window.addEventListener("keydown", e => keyDown = e.key)
68
+
window.addEventListener("keyup", _e => keyDown = null)
69
+
window.addEventListener("click", e => {
70
+
if(keyDown === "c"){
71
+
e.preventDefault()
72
+
e.stopImmediatePropagation()
73
+
reloader.openEditorAtCaller(e.target)
74
+
} else if(keyDown === "d"){
75
+
e.preventDefault()
76
+
e.stopImmediatePropagation()
77
+
reloader.openEditorAtDef(e.target)
78
+
}
79
+
}, true)
80
+
81
+
window.liveReloader = reloader
82
+
})
83
+
}
84
+
+32
assets/tsconfig.json
+32
assets/tsconfig.json
···
1
+
// This file is needed on most editors to enable the intelligent autocompletion
2
+
// of LiveView's JavaScript API methods. You can safely delete it if you don't need it.
3
+
//
4
+
// Note: This file assumes a basic esbuild setup without node_modules.
5
+
// We include a generic paths alias to deps to mimic how esbuild resolves
6
+
// the Phoenix and LiveView JavaScript assets.
7
+
// If you have a package.json in your project, you should remove the
8
+
// paths configuration and instead add the phoenix dependencies to the
9
+
// dependencies section of your package.json:
10
+
//
11
+
// {
12
+
// ...
13
+
// "dependencies": {
14
+
// ...,
15
+
// "phoenix": "../deps/phoenix",
16
+
// "phoenix_html": "../deps/phoenix_html",
17
+
// "phoenix_live_view": "../deps/phoenix_live_view"
18
+
// }
19
+
// }
20
+
//
21
+
// Feel free to adjust this configuration however you need.
22
+
{
23
+
"compilerOptions": {
24
+
"baseUrl": ".",
25
+
"paths": {
26
+
"*": ["../deps/*"]
27
+
},
28
+
"allowJs": true,
29
+
"noEmit": true
30
+
},
31
+
"include": ["js/**/*"]
32
+
}
+43
assets/vendor/heroicons.js
+43
assets/vendor/heroicons.js
···
1
+
const plugin = require("tailwindcss/plugin")
2
+
const fs = require("fs")
3
+
const path = require("path")
4
+
5
+
module.exports = plugin(function({matchComponents, theme}) {
6
+
let iconsDir = path.join(__dirname, "../../deps/heroicons/optimized")
7
+
let values = {}
8
+
let icons = [
9
+
["", "/24/outline"],
10
+
["-solid", "/24/solid"],
11
+
["-mini", "/20/solid"],
12
+
["-micro", "/16/solid"]
13
+
]
14
+
icons.forEach(([suffix, dir]) => {
15
+
fs.readdirSync(path.join(iconsDir, dir)).forEach(file => {
16
+
let name = path.basename(file, ".svg") + suffix
17
+
values[name] = {name, fullPath: path.join(iconsDir, dir, file)}
18
+
})
19
+
})
20
+
matchComponents({
21
+
"hero": ({name, fullPath}) => {
22
+
let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "")
23
+
content = encodeURIComponent(content)
24
+
let size = theme("spacing.6")
25
+
if (name.endsWith("-mini")) {
26
+
size = theme("spacing.5")
27
+
} else if (name.endsWith("-micro")) {
28
+
size = theme("spacing.4")
29
+
}
30
+
return {
31
+
[`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
32
+
"-webkit-mask": `var(--hero-${name})`,
33
+
"mask": `var(--hero-${name})`,
34
+
"mask-repeat": "no-repeat",
35
+
"background-color": "currentColor",
36
+
"vertical-align": "middle",
37
+
"display": "inline-block",
38
+
"width": size,
39
+
"height": size
40
+
}
41
+
}
42
+
}, {values})
43
+
})
+138
assets/vendor/topbar.js
+138
assets/vendor/topbar.js
···
1
+
/**
2
+
* @license MIT
3
+
* topbar 3.0.0
4
+
* http://buunguyen.github.io/topbar
5
+
* Copyright (c) 2024 Buu Nguyen
6
+
*/
7
+
(function (window, document) {
8
+
"use strict";
9
+
10
+
var canvas,
11
+
currentProgress,
12
+
showing,
13
+
progressTimerId = null,
14
+
fadeTimerId = null,
15
+
delayTimerId = null,
16
+
addEvent = function (elem, type, handler) {
17
+
if (elem.addEventListener) elem.addEventListener(type, handler, false);
18
+
else if (elem.attachEvent) elem.attachEvent("on" + type, handler);
19
+
else elem["on" + type] = handler;
20
+
},
21
+
options = {
22
+
autoRun: true,
23
+
barThickness: 3,
24
+
barColors: {
25
+
0: "rgba(26, 188, 156, .9)",
26
+
".25": "rgba(52, 152, 219, .9)",
27
+
".50": "rgba(241, 196, 15, .9)",
28
+
".75": "rgba(230, 126, 34, .9)",
29
+
"1.0": "rgba(211, 84, 0, .9)",
30
+
},
31
+
shadowBlur: 10,
32
+
shadowColor: "rgba(0, 0, 0, .6)",
33
+
className: null,
34
+
},
35
+
repaint = function () {
36
+
canvas.width = window.innerWidth;
37
+
canvas.height = options.barThickness * 5; // need space for shadow
38
+
39
+
var ctx = canvas.getContext("2d");
40
+
ctx.shadowBlur = options.shadowBlur;
41
+
ctx.shadowColor = options.shadowColor;
42
+
43
+
var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
44
+
for (var stop in options.barColors)
45
+
lineGradient.addColorStop(stop, options.barColors[stop]);
46
+
ctx.lineWidth = options.barThickness;
47
+
ctx.beginPath();
48
+
ctx.moveTo(0, options.barThickness / 2);
49
+
ctx.lineTo(
50
+
Math.ceil(currentProgress * canvas.width),
51
+
options.barThickness / 2
52
+
);
53
+
ctx.strokeStyle = lineGradient;
54
+
ctx.stroke();
55
+
},
56
+
createCanvas = function () {
57
+
canvas = document.createElement("canvas");
58
+
var style = canvas.style;
59
+
style.position = "fixed";
60
+
style.top = style.left = style.right = style.margin = style.padding = 0;
61
+
style.zIndex = 100001;
62
+
style.display = "none";
63
+
if (options.className) canvas.classList.add(options.className);
64
+
addEvent(window, "resize", repaint);
65
+
},
66
+
topbar = {
67
+
config: function (opts) {
68
+
for (var key in opts)
69
+
if (options.hasOwnProperty(key)) options[key] = opts[key];
70
+
},
71
+
show: function (delay) {
72
+
if (showing) return;
73
+
if (delay) {
74
+
if (delayTimerId) return;
75
+
delayTimerId = setTimeout(() => topbar.show(), delay);
76
+
} else {
77
+
showing = true;
78
+
if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId);
79
+
if (!canvas) createCanvas();
80
+
if (!canvas.parentElement) document.body.appendChild(canvas);
81
+
canvas.style.opacity = 1;
82
+
canvas.style.display = "block";
83
+
topbar.progress(0);
84
+
if (options.autoRun) {
85
+
(function loop() {
86
+
progressTimerId = window.requestAnimationFrame(loop);
87
+
topbar.progress(
88
+
"+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2)
89
+
);
90
+
})();
91
+
}
92
+
}
93
+
},
94
+
progress: function (to) {
95
+
if (typeof to === "undefined") return currentProgress;
96
+
if (typeof to === "string") {
97
+
to =
98
+
(to.indexOf("+") >= 0 || to.indexOf("-") >= 0
99
+
? currentProgress
100
+
: 0) + parseFloat(to);
101
+
}
102
+
currentProgress = to > 1 ? 1 : to;
103
+
repaint();
104
+
return currentProgress;
105
+
},
106
+
hide: function () {
107
+
clearTimeout(delayTimerId);
108
+
delayTimerId = null;
109
+
if (!showing) return;
110
+
showing = false;
111
+
if (progressTimerId != null) {
112
+
window.cancelAnimationFrame(progressTimerId);
113
+
progressTimerId = null;
114
+
}
115
+
(function loop() {
116
+
if (topbar.progress("+.1") >= 1) {
117
+
canvas.style.opacity -= 0.05;
118
+
if (canvas.style.opacity <= 0.05) {
119
+
canvas.style.display = "none";
120
+
fadeTimerId = null;
121
+
return;
122
+
}
123
+
}
124
+
fadeTimerId = window.requestAnimationFrame(loop);
125
+
})();
126
+
},
127
+
};
128
+
129
+
if (typeof module === "object" && typeof module.exports === "object") {
130
+
module.exports = topbar;
131
+
} else if (typeof define === "function" && define.amd) {
132
+
define(function () {
133
+
return topbar;
134
+
});
135
+
} else {
136
+
this.topbar = topbar;
137
+
}
138
+
}.call(this, window, document));
-677
bun.lock
-677
bun.lock
···
1
-
{
2
-
"lockfileVersion": 1,
3
-
"workspaces": {
4
-
"": {
5
-
"name": "comet",
6
-
"devDependencies": {
7
-
"@types/bun": "latest",
8
-
"prettier": "^3.5.3",
9
-
"prettier-plugin-svelte": "^3.4.0",
10
-
"prettier-plugin-tailwindcss": "^0.6.11",
11
-
},
12
-
"peerDependencies": {
13
-
"typescript": "^5",
14
-
},
15
-
},
16
-
"apps/frontend": {
17
-
"name": "comet",
18
-
"version": "0.0.1",
19
-
"devDependencies": {
20
-
"@eslint/compat": "^1.2.5",
21
-
"@eslint/js": "^9.18.0",
22
-
"@fontsource-variable/inter": "^5.2.5",
23
-
"@lucide/svelte": "^0.487.0",
24
-
"@sveltejs/adapter-auto": "^4.0.0",
25
-
"@sveltejs/kit": "^2.16.0",
26
-
"@sveltejs/vite-plugin-svelte": "^5.0.0",
27
-
"@tailwindcss/vite": "^4.0.0",
28
-
"bits-ui": "^1.3.17",
29
-
"clsx": "^2.1.1",
30
-
"eslint": "^9.18.0",
31
-
"eslint-config-prettier": "^10.0.1",
32
-
"eslint-plugin-svelte": "^3.0.0",
33
-
"globals": "^16.0.0",
34
-
"svelte": "^5.0.0",
35
-
"svelte-check": "^4.0.0",
36
-
"tailwindcss": "^4.0.0",
37
-
"typescript": "^5.0.0",
38
-
"typescript-eslint": "^8.20.0",
39
-
"vite": "^6.2.5",
40
-
},
41
-
},
42
-
"packages/lexicons": {
43
-
"name": "lexicons",
44
-
"dependencies": {
45
-
"@atproto/lexicon": "^0.4.11",
46
-
},
47
-
"devDependencies": {
48
-
"@atproto/lex-cli": "^0.8.1",
49
-
"@types/bun": "latest",
50
-
},
51
-
"peerDependencies": {
52
-
"typescript": "^5",
53
-
},
54
-
},
55
-
},
56
-
"packages": {
57
-
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
58
-
59
-
"@atproto/common-web": ["@atproto/common-web@0.4.2", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, "sha512-vrXwGNoFGogodjQvJDxAeP3QbGtawgZute2ed1XdRO0wMixLk3qewtikZm06H259QDJVu6voKC5mubml+WgQUw=="],
60
-
61
-
"@atproto/lex-cli": ["@atproto/lex-cli@0.8.1", "", { "dependencies": { "@atproto/lexicon": "^0.4.11", "@atproto/syntax": "^0.4.0", "chalk": "^4.1.2", "commander": "^9.4.0", "prettier": "^3.2.5", "ts-morph": "^24.0.0", "yesno": "^0.4.0", "zod": "^3.23.8" }, "bin": { "lex": "dist/index.js" } }, "sha512-0Ns6kX46gum2jU8bpvWCSVqoYhjmJrOGR/NLfLHgPbJtBlyxMGQAxqpy1x6zOi6SkkRGWYhHvRfr5J8lTHbxjA=="],
62
-
63
-
"@atproto/lexicon": ["@atproto/lexicon@0.4.11", "", { "dependencies": { "@atproto/common-web": "^0.4.2", "@atproto/syntax": "^0.4.0", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-btefdnvNz2Ao2I+qbmj0F06HC8IlrM/IBz6qOBS50r0S6uDf5tOO+Mv2tSVdimFkdzyDdLtBI1sV36ONxz2cOw=="],
64
-
65
-
"@atproto/syntax": ["@atproto/syntax@0.4.0", "", {}, "sha512-b9y5ceHS8YKOfP3mdKmwAx5yVj9294UN7FG2XzP6V5aKUdFazEYRnR9m5n5ZQFKa3GNvz7de9guZCJ/sUTcOAA=="],
66
-
67
-
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="],
68
-
69
-
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="],
70
-
71
-
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="],
72
-
73
-
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="],
74
-
75
-
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="],
76
-
77
-
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="],
78
-
79
-
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="],
80
-
81
-
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="],
82
-
83
-
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="],
84
-
85
-
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="],
86
-
87
-
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="],
88
-
89
-
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="],
90
-
91
-
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="],
92
-
93
-
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="],
94
-
95
-
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="],
96
-
97
-
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="],
98
-
99
-
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="],
100
-
101
-
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.4", "", { "os": "none", "cpu": "arm64" }, "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ=="],
102
-
103
-
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="],
104
-
105
-
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A=="],
106
-
107
-
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="],
108
-
109
-
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="],
110
-
111
-
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="],
112
-
113
-
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="],
114
-
115
-
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="],
116
-
117
-
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="],
118
-
119
-
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
120
-
121
-
"@eslint/compat": ["@eslint/compat@1.2.9", "", { "peerDependencies": { "eslint": "^9.10.0" }, "optionalPeers": ["eslint"] }, "sha512-gCdSY54n7k+driCadyMNv8JSPzYLeDVM/ikZRtvtROBpRdFSkS8W9A82MqsaY7lZuwL0wiapgD0NT1xT0hyJsA=="],
122
-
123
-
"@eslint/config-array": ["@eslint/config-array@0.20.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ=="],
124
-
125
-
"@eslint/config-helpers": ["@eslint/config-helpers@0.2.2", "", {}, "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg=="],
126
-
127
-
"@eslint/core": ["@eslint/core@0.14.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg=="],
128
-
129
-
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
130
-
131
-
"@eslint/js": ["@eslint/js@9.27.0", "", {}, "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA=="],
132
-
133
-
"@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="],
134
-
135
-
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.1", "", { "dependencies": { "@eslint/core": "^0.14.0", "levn": "^0.4.1" } }, "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w=="],
136
-
137
-
"@floating-ui/core": ["@floating-ui/core@1.7.0", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA=="],
138
-
139
-
"@floating-ui/dom": ["@floating-ui/dom@1.7.0", "", { "dependencies": { "@floating-ui/core": "^1.7.0", "@floating-ui/utils": "^0.2.9" } }, "sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg=="],
140
-
141
-
"@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="],
142
-
143
-
"@fontsource-variable/inter": ["@fontsource-variable/inter@5.2.5", "", {}, "sha512-TrWffUAFOnT8zroE9YmGybagoOgM/HjRqMQ8k9R0vVgXlnUh/vnpbGPAS/Caz1KIlOPnPGh6fvJbb7DHbFCncA=="],
144
-
145
-
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
146
-
147
-
"@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="],
148
-
149
-
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
150
-
151
-
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
152
-
153
-
"@internationalized/date": ["@internationalized/date@3.8.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-J51AJ0fEL68hE4CwGPa6E0PO6JDaVLd8aln48xFCSy7CZkZc96dGEGmLs2OEEbBxcsVZtfrqkXJwI2/MSG8yKw=="],
154
-
155
-
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
156
-
157
-
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
158
-
159
-
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
160
-
161
-
"@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="],
162
-
163
-
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="],
164
-
165
-
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
166
-
167
-
"@lucide/svelte": ["@lucide/svelte@0.487.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-27b/wUzWrqDJu97+1iSV2X8L2JGRWH/mAWAjHgazWxhGxVu/kS0p3SbNu6w3skNmQNEku33EKU1v44IVwULzbw=="],
168
-
169
-
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
170
-
171
-
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
172
-
173
-
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
174
-
175
-
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
176
-
177
-
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.40.2", "", { "os": "android", "cpu": "arm" }, "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg=="],
178
-
179
-
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.40.2", "", { "os": "android", "cpu": "arm64" }, "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw=="],
180
-
181
-
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.40.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w=="],
182
-
183
-
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.40.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ=="],
184
-
185
-
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.40.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ=="],
186
-
187
-
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.40.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q=="],
188
-
189
-
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.40.2", "", { "os": "linux", "cpu": "arm" }, "sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q=="],
190
-
191
-
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.40.2", "", { "os": "linux", "cpu": "arm" }, "sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg=="],
192
-
193
-
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.40.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg=="],
194
-
195
-
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.40.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg=="],
196
-
197
-
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.40.2", "", { "os": "linux", "cpu": "none" }, "sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw=="],
198
-
199
-
"@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.40.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q=="],
200
-
201
-
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.40.2", "", { "os": "linux", "cpu": "none" }, "sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg=="],
202
-
203
-
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.40.2", "", { "os": "linux", "cpu": "none" }, "sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg=="],
204
-
205
-
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.40.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ=="],
206
-
207
-
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.40.2", "", { "os": "linux", "cpu": "x64" }, "sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng=="],
208
-
209
-
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.40.2", "", { "os": "linux", "cpu": "x64" }, "sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA=="],
210
-
211
-
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.40.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg=="],
212
-
213
-
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.40.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA=="],
214
-
215
-
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.40.2", "", { "os": "win32", "cpu": "x64" }, "sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA=="],
216
-
217
-
"@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.5", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ=="],
218
-
219
-
"@sveltejs/adapter-auto": ["@sveltejs/adapter-auto@4.0.0", "", { "dependencies": { "import-meta-resolve": "^4.1.0" }, "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-kmuYSQdD2AwThymQF0haQhM8rE5rhutQXG4LNbnbShwhMO4qQGnKaaTy+88DuNSuoQDi58+thpq8XpHc1+oEKQ=="],
220
-
221
-
"@sveltejs/kit": ["@sveltejs/kit@2.21.0", "", { "dependencies": { "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.1.0", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0" }, "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-kvu4h9qXduiPk1Q1oqFKDLFGu/7mslEYbVaqpbBcBxjlRJnvNCFwEvEwKt0Mx9TtSi8J77xRelvJobrGlst4nQ=="],
222
-
223
-
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@5.0.3", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "debug": "^4.4.0", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.15", "vitefu": "^1.0.4" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw=="],
224
-
225
-
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@4.0.1", "", { "dependencies": { "debug": "^4.3.7" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.0", "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw=="],
226
-
227
-
"@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="],
228
-
229
-
"@tailwindcss/node": ["@tailwindcss/node@4.1.7", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.7" } }, "sha512-9rsOpdY9idRI2NH6CL4wORFY0+Q6fnx9XP9Ju+iq/0wJwGD5IByIgFmwVbyy4ymuyprj8Qh4ErxMKTUL4uNh3g=="],
230
-
231
-
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.7", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.7", "@tailwindcss/oxide-darwin-arm64": "4.1.7", "@tailwindcss/oxide-darwin-x64": "4.1.7", "@tailwindcss/oxide-freebsd-x64": "4.1.7", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.7", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.7", "@tailwindcss/oxide-linux-arm64-musl": "4.1.7", "@tailwindcss/oxide-linux-x64-gnu": "4.1.7", "@tailwindcss/oxide-linux-x64-musl": "4.1.7", "@tailwindcss/oxide-wasm32-wasi": "4.1.7", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.7", "@tailwindcss/oxide-win32-x64-msvc": "4.1.7" } }, "sha512-5SF95Ctm9DFiUyjUPnDGkoKItPX/k+xifcQhcqX5RA85m50jw1pT/KzjdvlqxRja45Y52nR4MR9fD1JYd7f8NQ=="],
232
-
233
-
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.7", "", { "os": "android", "cpu": "arm64" }, "sha512-IWA410JZ8fF7kACus6BrUwY2Z1t1hm0+ZWNEzykKmMNM09wQooOcN/VXr0p/WJdtHZ90PvJf2AIBS/Ceqx1emg=="],
234
-
235
-
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-81jUw9To7fimGGkuJ2W5h3/oGonTOZKZ8C2ghm/TTxbwvfSiFSDPd6/A/KE2N7Jp4mv3Ps9OFqg2fEKgZFfsvg=="],
236
-
237
-
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-q77rWjEyGHV4PdDBtrzO0tgBBPlQWKY7wZK0cUok/HaGgbNKecegNxCGikuPJn5wFAlIywC3v+WMBt0PEBtwGw=="],
238
-
239
-
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-RfmdbbK6G6ptgF4qqbzoxmH+PKfP4KSVs7SRlTwcbRgBwezJkAO3Qta/7gDy10Q2DcUVkKxFLXUQO6J3CRvBGw=="],
240
-
241
-
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.7", "", { "os": "linux", "cpu": "arm" }, "sha512-OZqsGvpwOa13lVd1z6JVwQXadEobmesxQ4AxhrwRiPuE04quvZHWn/LnihMg7/XkN+dTioXp/VMu/p6A5eZP3g=="],
242
-
243
-
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-voMvBTnJSfKecJxGkoeAyW/2XRToLZ227LxswLAwKY7YslG/Xkw9/tJNH+3IVh5bdYzYE7DfiaPbRkSHFxY1xA=="],
244
-
245
-
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-PjGuNNmJeKHnP58M7XyjJyla8LPo+RmwHQpBI+W/OxqrwojyuCQ+GUtygu7jUqTEexejZHr/z3nBc/gTiXBj4A=="],
246
-
247
-
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.7", "", { "os": "linux", "cpu": "x64" }, "sha512-HMs+Va+ZR3gC3mLZE00gXxtBo3JoSQxtu9lobbZd+DmfkIxR54NO7Z+UQNPsa0P/ITn1TevtFxXTpsRU7qEvWg=="],
248
-
249
-
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.7", "", { "os": "linux", "cpu": "x64" }, "sha512-MHZ6jyNlutdHH8rd+YTdr3QbXrHXqwIhHw9e7yXEBcQdluGwhpQY2Eku8UZK6ReLaWtQ4gijIv5QoM5eE+qlsA=="],
250
-
251
-
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.7", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@emnapi/wasi-threads": "^1.0.2", "@napi-rs/wasm-runtime": "^0.2.9", "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-ANaSKt74ZRzE2TvJmUcbFQ8zS201cIPxUDm5qez5rLEwWkie2SkGtA4P+GPTj+u8N6JbPrC8MtY8RmJA35Oo+A=="],
252
-
253
-
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-HUiSiXQ9gLJBAPCMVRk2RT1ZrBjto7WvqsPBwUrNK2BcdSxMnk19h4pjZjI7zgPhDxlAbJSumTC4ljeA9y0tEw=="],
254
-
255
-
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.7", "", { "os": "win32", "cpu": "x64" }, "sha512-rYHGmvoHiLJ8hWucSfSOEmdCBIGZIq7SpkPRSqLsH2Ab2YUNgKeAPT1Fi2cx3+hnYOrAb0jp9cRyode3bBW4mQ=="],
256
-
257
-
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.7", "", { "dependencies": { "@tailwindcss/node": "4.1.7", "@tailwindcss/oxide": "4.1.7", "tailwindcss": "4.1.7" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-tYa2fO3zDe41I7WqijyVbRd8oWT0aEID1Eokz5hMT6wShLIHj3yvwj9XbfuloHP9glZ6H+aG2AN/+ZrxJ1Y5RQ=="],
258
-
259
-
"@ts-morph/common": ["@ts-morph/common@0.25.0", "", { "dependencies": { "minimatch": "^9.0.4", "path-browserify": "^1.0.1", "tinyglobby": "^0.2.9" } }, "sha512-kMnZz+vGGHi4GoHnLmMhGNjm44kGtKUXGnOvrKmMwAuvNjM/PgKVGfUnL7IDvK7Jb2QQ82jq3Zmp04Gy+r3Dkg=="],
260
-
261
-
"@types/bun": ["@types/bun@1.2.13", "", { "dependencies": { "bun-types": "1.2.13" } }, "sha512-u6vXep/i9VBxoJl3GjZsl/BFIsvML8DfVDO0RYLEwtSZSp981kEO1V5NwRcO1CPJ7AmvpbnDCiMKo3JvbDEjAg=="],
262
-
263
-
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
264
-
265
-
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
266
-
267
-
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
268
-
269
-
"@types/node": ["@types/node@22.15.18", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg=="],
270
-
271
-
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.32.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/type-utils": "8.32.1", "@typescript-eslint/utils": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg=="],
272
-
273
-
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.32.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/types": "8.32.1", "@typescript-eslint/typescript-estree": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg=="],
274
-
275
-
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.32.1", "", { "dependencies": { "@typescript-eslint/types": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1" } }, "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA=="],
276
-
277
-
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.32.1", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.32.1", "@typescript-eslint/utils": "8.32.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA=="],
278
-
279
-
"@typescript-eslint/types": ["@typescript-eslint/types@8.32.1", "", {}, "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg=="],
280
-
281
-
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.32.1", "", { "dependencies": { "@typescript-eslint/types": "8.32.1", "@typescript-eslint/visitor-keys": "8.32.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg=="],
282
-
283
-
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.32.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/types": "8.32.1", "@typescript-eslint/typescript-estree": "8.32.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA=="],
284
-
285
-
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.32.1", "", { "dependencies": { "@typescript-eslint/types": "8.32.1", "eslint-visitor-keys": "^4.2.0" } }, "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w=="],
286
-
287
-
"acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="],
288
-
289
-
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
290
-
291
-
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
292
-
293
-
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
294
-
295
-
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
296
-
297
-
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
298
-
299
-
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
300
-
301
-
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
302
-
303
-
"bits-ui": ["bits-ui@1.4.8", "", { "dependencies": { "@floating-ui/core": "^1.6.4", "@floating-ui/dom": "^1.6.7", "@internationalized/date": "^3.5.6", "esm-env": "^1.1.2", "runed": "^0.23.2", "svelte-toolbelt": "^0.7.1", "tabbable": "^6.2.0" }, "peerDependencies": { "svelte": "^5.11.0" } }, "sha512-j34GsdSsJ+ZBl9h/70VkufvrlEgTKQSZvm80eM5VvuhLJWvpfEpn9+k0FVmtDQl9NSPgEVtI9imYhm8nW9Nj/w=="],
304
-
305
-
"brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
306
-
307
-
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
308
-
309
-
"bun-types": ["bun-types@1.2.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-rRjA1T6n7wto4gxhAO/ErZEtOXyEZEmnIHQfl0Dt1QQSB4QV0iP6BZ9/YB5fZaHFQ2dwHFrmPaRQ9GGMX01k9Q=="],
310
-
311
-
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
312
-
313
-
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
314
-
315
-
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
316
-
317
-
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
318
-
319
-
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
320
-
321
-
"code-block-writer": ["code-block-writer@13.0.3", "", {}, "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg=="],
322
-
323
-
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
324
-
325
-
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
326
-
327
-
"comet": ["comet@workspace:apps/frontend"],
328
-
329
-
"commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="],
330
-
331
-
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
332
-
333
-
"cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
334
-
335
-
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
336
-
337
-
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
338
-
339
-
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
340
-
341
-
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
342
-
343
-
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
344
-
345
-
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
346
-
347
-
"devalue": ["devalue@5.1.1", "", {}, "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw=="],
348
-
349
-
"enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="],
350
-
351
-
"esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="],
352
-
353
-
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
354
-
355
-
"eslint": ["eslint@9.27.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.20.0", "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.27.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q=="],
356
-
357
-
"eslint-config-prettier": ["eslint-config-prettier@10.1.5", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw=="],
358
-
359
-
"eslint-plugin-svelte": ["eslint-plugin-svelte@3.8.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.6.1", "@jridgewell/sourcemap-codec": "^1.5.0", "esutils": "^2.0.3", "globals": "^16.0.0", "known-css-properties": "^0.36.0", "postcss": "^8.4.49", "postcss-load-config": "^3.1.4", "postcss-safe-parser": "^7.0.0", "semver": "^7.6.3", "svelte-eslint-parser": "^1.2.0" }, "peerDependencies": { "eslint": "^8.57.1 || ^9.0.0", "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-w6NQifz1xBIFcs4XMLYT36xukgN1xgzoPGk1nJLC1UeZ8O4CUCCSZx1tgwq3yRNkMSXMUqbMJjslFweWsDlAoA=="],
360
-
361
-
"eslint-scope": ["eslint-scope@8.3.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ=="],
362
-
363
-
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="],
364
-
365
-
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
366
-
367
-
"espree": ["espree@10.3.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.0" } }, "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg=="],
368
-
369
-
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
370
-
371
-
"esrap": ["esrap@1.4.6", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw=="],
372
-
373
-
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
374
-
375
-
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
376
-
377
-
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
378
-
379
-
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
380
-
381
-
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
382
-
383
-
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
384
-
385
-
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
386
-
387
-
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
388
-
389
-
"fdir": ["fdir@6.4.4", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg=="],
390
-
391
-
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
392
-
393
-
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
394
-
395
-
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
396
-
397
-
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
398
-
399
-
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
400
-
401
-
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
402
-
403
-
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
404
-
405
-
"globals": ["globals@16.1.0", "", {}, "sha512-aibexHNbb/jiUSObBgpHLj+sIuUmJnYcgXBlrfsiDZ9rt4aF2TFRbyLgZ2iFQuVZ1K5Mx3FVkbKRSgKrbK3K2g=="],
406
-
407
-
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
408
-
409
-
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
410
-
411
-
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
412
-
413
-
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
414
-
415
-
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
416
-
417
-
"import-meta-resolve": ["import-meta-resolve@4.1.0", "", {}, "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw=="],
418
-
419
-
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
420
-
421
-
"inline-style-parser": ["inline-style-parser@0.2.4", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="],
422
-
423
-
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
424
-
425
-
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
426
-
427
-
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
428
-
429
-
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
430
-
431
-
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
432
-
433
-
"iso-datestring-validator": ["iso-datestring-validator@2.2.2", "", {}, "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA=="],
434
-
435
-
"jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
436
-
437
-
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
438
-
439
-
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
440
-
441
-
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
442
-
443
-
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
444
-
445
-
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
446
-
447
-
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
448
-
449
-
"known-css-properties": ["known-css-properties@0.36.0", "", {}, "sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA=="],
450
-
451
-
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
452
-
453
-
"lexicons": ["lexicons@workspace:packages/lexicons"],
454
-
455
-
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
456
-
457
-
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="],
458
-
459
-
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="],
460
-
461
-
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig=="],
462
-
463
-
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.1", "", { "os": "linux", "cpu": "arm" }, "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q=="],
464
-
465
-
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw=="],
466
-
467
-
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ=="],
468
-
469
-
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw=="],
470
-
471
-
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ=="],
472
-
473
-
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA=="],
474
-
475
-
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="],
476
-
477
-
"lilconfig": ["lilconfig@2.1.0", "", {}, "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="],
478
-
479
-
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
480
-
481
-
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
482
-
483
-
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
484
-
485
-
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
486
-
487
-
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
488
-
489
-
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
490
-
491
-
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
492
-
493
-
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
494
-
495
-
"minizlib": ["minizlib@3.0.2", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA=="],
496
-
497
-
"mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
498
-
499
-
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
500
-
501
-
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
502
-
503
-
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
504
-
505
-
"multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
506
-
507
-
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
508
-
509
-
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
510
-
511
-
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
512
-
513
-
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
514
-
515
-
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
516
-
517
-
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
518
-
519
-
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
520
-
521
-
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
522
-
523
-
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
524
-
525
-
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
526
-
527
-
"picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
528
-
529
-
"postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
530
-
531
-
"postcss-load-config": ["postcss-load-config@3.1.4", "", { "dependencies": { "lilconfig": "^2.0.5", "yaml": "^1.10.2" }, "peerDependencies": { "postcss": ">=8.0.9", "ts-node": ">=9.0.0" }, "optionalPeers": ["postcss", "ts-node"] }, "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg=="],
532
-
533
-
"postcss-safe-parser": ["postcss-safe-parser@7.0.1", "", { "peerDependencies": { "postcss": "^8.4.31" } }, "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A=="],
534
-
535
-
"postcss-scss": ["postcss-scss@4.0.9", "", { "peerDependencies": { "postcss": "^8.4.29" } }, "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A=="],
536
-
537
-
"postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
538
-
539
-
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
540
-
541
-
"prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
542
-
543
-
"prettier-plugin-svelte": ["prettier-plugin-svelte@3.4.0", "", { "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" } }, "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ=="],
544
-
545
-
"prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.6.11", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-import-sort": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-style-order": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-import-sort", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-style-order", "prettier-plugin-svelte"] }, "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA=="],
546
-
547
-
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
548
-
549
-
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
550
-
551
-
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
552
-
553
-
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
554
-
555
-
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
556
-
557
-
"rollup": ["rollup@4.40.2", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.40.2", "@rollup/rollup-android-arm64": "4.40.2", "@rollup/rollup-darwin-arm64": "4.40.2", "@rollup/rollup-darwin-x64": "4.40.2", "@rollup/rollup-freebsd-arm64": "4.40.2", "@rollup/rollup-freebsd-x64": "4.40.2", "@rollup/rollup-linux-arm-gnueabihf": "4.40.2", "@rollup/rollup-linux-arm-musleabihf": "4.40.2", "@rollup/rollup-linux-arm64-gnu": "4.40.2", "@rollup/rollup-linux-arm64-musl": "4.40.2", "@rollup/rollup-linux-loongarch64-gnu": "4.40.2", "@rollup/rollup-linux-powerpc64le-gnu": "4.40.2", "@rollup/rollup-linux-riscv64-gnu": "4.40.2", "@rollup/rollup-linux-riscv64-musl": "4.40.2", "@rollup/rollup-linux-s390x-gnu": "4.40.2", "@rollup/rollup-linux-x64-gnu": "4.40.2", "@rollup/rollup-linux-x64-musl": "4.40.2", "@rollup/rollup-win32-arm64-msvc": "4.40.2", "@rollup/rollup-win32-ia32-msvc": "4.40.2", "@rollup/rollup-win32-x64-msvc": "4.40.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg=="],
558
-
559
-
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
560
-
561
-
"runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="],
562
-
563
-
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
564
-
565
-
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
566
-
567
-
"set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
568
-
569
-
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
570
-
571
-
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
572
-
573
-
"sirv": ["sirv@3.0.1", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A=="],
574
-
575
-
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
576
-
577
-
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
578
-
579
-
"style-to-object": ["style-to-object@1.0.8", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g=="],
580
-
581
-
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
582
-
583
-
"svelte": ["svelte@5.30.2", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^1.4.6", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-zfGFEwwPeILToOxOqQyFq/vc8euXrX2XyoffkBNgn/k8D1nxbLt5+mNaqQBmZF/vVhBGmkY6VmNK18p9Gf0auQ=="],
584
-
585
-
"svelte-check": ["svelte-check@4.2.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-e49SU1RStvQhoipkQ/aonDhHnG3qxHSBtNfBRb9pxVXoa+N7qybAo32KgA9wEb2PCYFNaDg7bZCdhLD1vHpdYA=="],
586
-
587
-
"svelte-eslint-parser": ["svelte-eslint-parser@1.2.0", "", { "dependencies": { "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.0", "postcss": "^8.4.49", "postcss-scss": "^4.0.9", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-mbPtajIeuiyU80BEyGvwAktBeTX7KCr5/0l+uRGLq1dafwRNrjfM5kHGJScEBlPG3ipu6dJqfW/k0/fujvIEVw=="],
588
-
589
-
"svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
590
-
591
-
"tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="],
592
-
593
-
"tailwindcss": ["tailwindcss@4.1.7", "", {}, "sha512-kr1o/ErIdNhTz8uzAYL7TpaUuzKIE6QPQ4qmSdxnoX/lo+5wmUHQA6h3L5yIqEImSRnAAURDirLu/BgiXGPAhg=="],
594
-
595
-
"tapable": ["tapable@2.2.1", "", {}, "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="],
596
-
597
-
"tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="],
598
-
599
-
"tinyglobby": ["tinyglobby@0.2.13", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw=="],
600
-
601
-
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
602
-
603
-
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
604
-
605
-
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
606
-
607
-
"ts-morph": ["ts-morph@24.0.0", "", { "dependencies": { "@ts-morph/common": "~0.25.0", "code-block-writer": "^13.0.3" } }, "sha512-2OAOg/Ob5yx9Et7ZX4CvTCc0UFoZHwLEJ+dpDPSUi5TgwwlTlX47w+iFRrEwzUZwYACjq83cgjS/Da50Ga37uw=="],
608
-
609
-
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
610
-
611
-
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
612
-
613
-
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
614
-
615
-
"typescript-eslint": ["typescript-eslint@8.32.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.32.1", "@typescript-eslint/parser": "8.32.1", "@typescript-eslint/utils": "8.32.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg=="],
616
-
617
-
"uint8arrays": ["uint8arrays@3.0.0", "", { "dependencies": { "multiformats": "^9.4.2" } }, "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA=="],
618
-
619
-
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
620
-
621
-
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
622
-
623
-
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
624
-
625
-
"vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="],
626
-
627
-
"vitefu": ["vitefu@1.0.6", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" }, "optionalPeers": ["vite"] }, "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA=="],
628
-
629
-
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
630
-
631
-
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
632
-
633
-
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
634
-
635
-
"yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="],
636
-
637
-
"yesno": ["yesno@0.4.0", "", {}, "sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA=="],
638
-
639
-
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
640
-
641
-
"zimmerframe": ["zimmerframe@1.1.2", "", {}, "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="],
642
-
643
-
"zod": ["zod@3.24.4", "", {}, "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg=="],
644
-
645
-
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
646
-
647
-
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
648
-
649
-
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
650
-
651
-
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
652
-
653
-
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
654
-
655
-
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA=="],
656
-
657
-
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.10", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.9.0" }, "bundled": true }, "sha512-bCsCyeZEwVErsGmyPNSzwfwFn4OdxBj0mmv6hOFucB/k81Ojdu68RbZdxYsRQUPc9l6SU5F/cG+bXgWs3oUgsQ=="],
658
-
659
-
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="],
660
-
661
-
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
662
-
663
-
"@ts-morph/common/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
664
-
665
-
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.4", "", {}, "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A=="],
666
-
667
-
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
668
-
669
-
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
670
-
671
-
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
672
-
673
-
"@ts-morph/common/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
674
-
675
-
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
676
-
}
677
-
}
+71
config/config.exs
+71
config/config.exs
···
1
+
# This file is responsible for configuring your application
2
+
# and its dependencies with the aid of the Config module.
3
+
#
4
+
# This configuration file is loaded before any dependency and
5
+
# is restricted to this project.
6
+
7
+
# General application configuration
8
+
import Config
9
+
10
+
config :comet,
11
+
ecto_repos: [Comet.Repo],
12
+
generators: [timestamp_type: :utc_datetime, binary_id: true]
13
+
14
+
# Configure the endpoint
15
+
config :comet, CometWeb.Endpoint,
16
+
url: [host: "localhost"],
17
+
adapter: Bandit.PhoenixAdapter,
18
+
render_errors: [
19
+
formats: [html: CometWeb.ErrorHTML, json: CometWeb.ErrorJSON],
20
+
layout: false
21
+
],
22
+
pubsub_server: Comet.PubSub,
23
+
live_view: [signing_salt: "ObastmTN"]
24
+
25
+
# Configure the mailer
26
+
#
27
+
# By default it uses the "Local" adapter which stores the emails
28
+
# locally. You can see the emails in your browser, at "/dev/mailbox".
29
+
#
30
+
# For production it's recommended to configure a different adapter
31
+
# at the `config/runtime.exs`.
32
+
config :comet, Comet.Mailer, adapter: Swoosh.Adapters.Local
33
+
34
+
# Configure esbuild (the version is required)
35
+
config :esbuild,
36
+
version: "0.25.4",
37
+
comet: [
38
+
args:
39
+
~w(js/app.js --bundle --target=es2022 --outdir=../priv/static/assets/js --external:/fonts/* --external:/images/* --loader:.woff2=file --alias:@=.),
40
+
cd: Path.expand("../assets", __DIR__),
41
+
env: %{"NODE_PATH" => [Path.expand("../deps", __DIR__), Mix.Project.build_path()]}
42
+
]
43
+
44
+
# Configure tailwind (the version is required)
45
+
config :tailwind,
46
+
version: "4.1.12",
47
+
comet: [
48
+
args: ~w(
49
+
--input=assets/css/app.css
50
+
--output=priv/static/assets/css/app.css
51
+
),
52
+
cd: Path.expand("..", __DIR__)
53
+
],
54
+
version_check: false,
55
+
path:
56
+
System.get_env(
57
+
"TAILWINDCSS_PATH",
58
+
Path.expand("../assets/node_modules/.bin/tailwindcss", __DIR__)
59
+
)
60
+
61
+
# Configure Elixir's Logger
62
+
config :logger, :default_formatter,
63
+
format: "$time $metadata[$level] $message\n",
64
+
metadata: [:request_id]
65
+
66
+
# Use Jason for JSON parsing in Phoenix
67
+
config :phoenix, :json_library, Jason
68
+
69
+
# Import environment specific config. This must remain at the bottom
70
+
# of this file so it overrides the configuration defined above.
71
+
import_config "#{config_env()}.exs"
+92
config/dev.exs
+92
config/dev.exs
···
1
+
import Config
2
+
3
+
# Configure your database
4
+
config :comet, Comet.Repo,
5
+
username: "comet",
6
+
password: "comet",
7
+
hostname: "localhost",
8
+
database: "comet_dev",
9
+
stacktrace: true,
10
+
show_sensitive_data_on_connection_error: true,
11
+
pool_size: 10
12
+
13
+
# For development, we disable any cache and enable
14
+
# debugging and code reloading.
15
+
#
16
+
# The watchers configuration can be used to run external
17
+
# watchers to your application. For example, we can use it
18
+
# to bundle .js and .css sources.
19
+
config :comet, CometWeb.Endpoint,
20
+
# Binding to loopback ipv4 address prevents access from other machines.
21
+
# Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
22
+
http: [ip: {127, 0, 0, 1}],
23
+
check_origin: false,
24
+
code_reloader: true,
25
+
debug_errors: true,
26
+
secret_key_base: "qEVbyaHJ1+52Mfh7Kfoc94yEwNc/e5vkRDUcAR4b3DofYJg7LgUjm4kd+3u+RelM",
27
+
watchers: [
28
+
esbuild: {Esbuild, :install_and_run, [:comet, ~w(--sourcemap=inline --watch)]},
29
+
tailwind: {Tailwind, :install_and_run, [:comet, ~w(--watch)]}
30
+
]
31
+
32
+
# ## SSL Support
33
+
#
34
+
# In order to use HTTPS in development, a self-signed
35
+
# certificate can be generated by running the following
36
+
# Mix task:
37
+
#
38
+
# mix phx.gen.cert
39
+
#
40
+
# Run `mix help phx.gen.cert` for more information.
41
+
#
42
+
# The `http:` config above can be replaced with:
43
+
#
44
+
# https: [
45
+
# port: 4001,
46
+
# cipher_suite: :strong,
47
+
# keyfile: "priv/cert/selfsigned_key.pem",
48
+
# certfile: "priv/cert/selfsigned.pem"
49
+
# ],
50
+
#
51
+
# If desired, both `http:` and `https:` keys can be
52
+
# configured to run both http and https servers on
53
+
# different ports.
54
+
55
+
# Reload browser tabs when matching files change.
56
+
config :comet, CometWeb.Endpoint,
57
+
live_reload: [
58
+
web_console_logger: true,
59
+
patterns: [
60
+
# Static assets, except user uploads
61
+
~r"priv/static/(?!uploads/).*\.(js|css|png|jpeg|jpg|gif|svg)$",
62
+
# Gettext translations
63
+
~r"priv/gettext/.*\.po$",
64
+
# Router, Controllers, LiveViews and LiveComponents
65
+
~r"lib/comet_web/router\.ex$",
66
+
~r"lib/comet_web/(controllers|live|components)/.*\.(ex|heex)$"
67
+
]
68
+
]
69
+
70
+
# Enable dev routes for dashboard and mailbox
71
+
config :comet, dev_routes: true
72
+
73
+
# Do not include metadata nor timestamps in development logs
74
+
config :logger, :default_formatter, format: "[$level] $message\n"
75
+
76
+
# Set a higher stacktrace during development. Avoid configuring such
77
+
# in production as building large stacktraces may be expensive.
78
+
config :phoenix, :stacktrace_depth, 20
79
+
80
+
# Initialize plugs at runtime for faster development compilation
81
+
config :phoenix, :plug_init_mode, :runtime
82
+
83
+
config :phoenix_live_view,
84
+
# Include debug annotations and locations in rendered markup.
85
+
# Changing this configuration will require mix clean and a full recompile.
86
+
debug_heex_annotations: true,
87
+
debug_attributes: true,
88
+
# Enable helpful, but potentially expensive runtime checks
89
+
enable_expensive_runtime_checks: true
90
+
91
+
# Disable swoosh api client as it is only required for production adapters.
92
+
config :swoosh, :api_client, false
+24
config/prod.exs
+24
config/prod.exs
···
1
+
import Config
2
+
3
+
# Note we also include the path to a cache manifest
4
+
# containing the digested version of static files. This
5
+
# manifest is generated by the `mix assets.deploy` task,
6
+
# which you should run after static files are built and
7
+
# before starting your production server.
8
+
config :comet, CometWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json"
9
+
10
+
# Force using SSL in production. This also sets the "strict-security-transport" header,
11
+
# also known as HSTS. `:force_ssl` is required to be set at compile-time.
12
+
config :comet, CometWeb.Endpoint, force_ssl: [rewrite_on: [:x_forwarded_proto]]
13
+
14
+
# Configure Swoosh API Client
15
+
config :swoosh, api_client: Swoosh.ApiClient.Req
16
+
17
+
# Disable Swoosh Local Memory Storage
18
+
config :swoosh, local: false
19
+
20
+
# Do not print debug messages in production
21
+
config :logger, level: :info
22
+
23
+
# Runtime production configuration, including reading
24
+
# of environment variables, is done on config/runtime.exs.
+119
config/runtime.exs
+119
config/runtime.exs
···
1
+
import Config
2
+
3
+
# config/runtime.exs is executed for all environments, including
4
+
# during releases. It is executed after compilation and before the
5
+
# system starts, so it is typically used to load production configuration
6
+
# and secrets from environment variables or elsewhere. Do not define
7
+
# any compile-time configuration in here, as it won't be applied.
8
+
# The block below contains prod specific runtime configuration.
9
+
10
+
# ## Using releases
11
+
#
12
+
# If you use `mix release`, you need to explicitly enable the server
13
+
# by passing the PHX_SERVER=true when you start it:
14
+
#
15
+
# PHX_SERVER=true bin/comet start
16
+
#
17
+
# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server`
18
+
# script that automatically sets the env var above.
19
+
if System.get_env("PHX_SERVER") do
20
+
config :comet, CometWeb.Endpoint, server: true
21
+
end
22
+
23
+
config :comet, CometWeb.Endpoint, http: [port: String.to_integer(System.get_env("PORT", "4000"))]
24
+
25
+
if config_env() == :prod do
26
+
database_url =
27
+
System.get_env("DATABASE_URL") ||
28
+
raise """
29
+
environment variable DATABASE_URL is missing.
30
+
For example: ecto://USER:PASS@HOST/DATABASE
31
+
"""
32
+
33
+
maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: []
34
+
35
+
config :comet, Comet.Repo,
36
+
# ssl: true,
37
+
url: database_url,
38
+
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
39
+
# For machines with several cores, consider starting multiple pools of `pool_size`
40
+
# pool_count: 4,
41
+
socket_options: maybe_ipv6
42
+
43
+
# The secret key base is used to sign/encrypt cookies and other secrets.
44
+
# A default value is used in config/dev.exs and config/test.exs but you
45
+
# want to use a different value for prod and you most likely don't want
46
+
# to check this value into version control, so we use an environment
47
+
# variable instead.
48
+
secret_key_base =
49
+
System.get_env("SECRET_KEY_BASE") ||
50
+
raise """
51
+
environment variable SECRET_KEY_BASE is missing.
52
+
You can generate one by calling: mix phx.gen.secret
53
+
"""
54
+
55
+
host = System.get_env("PHX_HOST") || "example.com"
56
+
57
+
config :comet, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")
58
+
59
+
config :comet, CometWeb.Endpoint,
60
+
url: [host: host, port: 443, scheme: "https"],
61
+
http: [
62
+
# Enable IPv6 and bind on all interfaces.
63
+
# Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.
64
+
# See the documentation on https://hexdocs.pm/bandit/Bandit.html#t:options/0
65
+
# for details about using IPv6 vs IPv4 and loopback vs public addresses.
66
+
ip: {0, 0, 0, 0, 0, 0, 0, 0}
67
+
],
68
+
secret_key_base: secret_key_base
69
+
70
+
# ## SSL Support
71
+
#
72
+
# To get SSL working, you will need to add the `https` key
73
+
# to your endpoint configuration:
74
+
#
75
+
# config :comet, CometWeb.Endpoint,
76
+
# https: [
77
+
# ...,
78
+
# port: 443,
79
+
# cipher_suite: :strong,
80
+
# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
81
+
# certfile: System.get_env("SOME_APP_SSL_CERT_PATH")
82
+
# ]
83
+
#
84
+
# The `cipher_suite` is set to `:strong` to support only the
85
+
# latest and more secure SSL ciphers. This means old browsers
86
+
# and clients may not be supported. You can set it to
87
+
# `:compatible` for wider support.
88
+
#
89
+
# `:keyfile` and `:certfile` expect an absolute path to the key
90
+
# and cert in disk or a relative path inside priv, for example
91
+
# "priv/ssl/server.key". For all supported SSL configuration
92
+
# options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1
93
+
#
94
+
# We also recommend setting `force_ssl` in your config/prod.exs,
95
+
# ensuring no data is ever sent via http, always redirecting to https:
96
+
#
97
+
# config :comet, CometWeb.Endpoint,
98
+
# force_ssl: [hsts: true]
99
+
#
100
+
# Check `Plug.SSL` for all available options in `force_ssl`.
101
+
102
+
# ## Configuring the mailer
103
+
#
104
+
# In production you need to configure the mailer to use a different adapter.
105
+
# Here is an example configuration for Mailgun:
106
+
#
107
+
# config :comet, Comet.Mailer,
108
+
# adapter: Swoosh.Adapters.Mailgun,
109
+
# api_key: System.get_env("MAILGUN_API_KEY"),
110
+
# domain: System.get_env("MAILGUN_DOMAIN")
111
+
#
112
+
# Most non-SMTP adapters require an API client. Swoosh supports Req, Hackney,
113
+
# and Finch out-of-the-box. This configuration is typically done at
114
+
# compile-time in your config/prod.exs:
115
+
#
116
+
# config :swoosh, :api_client, Swoosh.ApiClient.Req
117
+
#
118
+
# See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details.
119
+
end
+37
config/test.exs
+37
config/test.exs
···
1
+
import Config
2
+
3
+
# Configure your database
4
+
#
5
+
# The MIX_TEST_PARTITION environment variable can be used
6
+
# to provide built-in test partitioning in CI environment.
7
+
# Run `mix help test` for more information.
8
+
config :comet, Comet.Repo,
9
+
username: "postgres",
10
+
password: "postgres",
11
+
hostname: "localhost",
12
+
database: "comet_test#{System.get_env("MIX_TEST_PARTITION")}",
13
+
pool: Ecto.Adapters.SQL.Sandbox,
14
+
pool_size: System.schedulers_online() * 2
15
+
16
+
# We don't run a server during test. If one is required,
17
+
# you can enable the server option below.
18
+
config :comet, CometWeb.Endpoint,
19
+
http: [ip: {127, 0, 0, 1}, port: 4002],
20
+
secret_key_base: "p9+pymZBaKsKSlOfPLjw9HWpolaQwJSmVaepPAdfGpv3YUp/BO5SHkaS+Faavmec",
21
+
server: false
22
+
23
+
# In test we don't send emails
24
+
config :comet, Comet.Mailer, adapter: Swoosh.Adapters.Test
25
+
26
+
# Disable swoosh api client as it is only required for production adapters
27
+
config :swoosh, :api_client, false
28
+
29
+
# Print only warnings and errors during test
30
+
config :logger, level: :warning
31
+
32
+
# Initialize plugs at runtime for faster test compilation
33
+
config :phoenix, :plug_init_mode, :runtime
34
+
35
+
# Enable helpful, but potentially expensive runtime checks
36
+
config :phoenix_live_view,
37
+
enable_expensive_runtime_checks: true
+15
docker-compose.yml
+15
docker-compose.yml
···
1
+
services:
2
+
postgres:
3
+
image: postgres:17-alpine
4
+
restart: unless-stopped
5
+
ports:
6
+
- 127.0.0.1:5432:5432
7
+
environment:
8
+
POSTGRES_DB: comet_dev
9
+
POSTGRES_USER: comet
10
+
POSTGRES_PASSWORD: comet
11
+
volumes:
12
+
- postgres:/var/lib/postgresql/data
13
+
14
+
volumes:
15
+
postgres:
+8
-3
flake.nix
+8
-3
flake.nix
···
14
14
defaultForSystems = fn: forSystems (pkgs: {default = fn pkgs;});
15
15
in {
16
16
devShells = defaultForSystems (pkgs:
17
-
pkgs.mkShell {
18
-
nativeBuildInputs = with pkgs; [nodejs_22 bun];
19
-
});
17
+
with pkgs;
18
+
mkShell {
19
+
nativeBuildInputs = [elixir erlang nodejs pnpm tailwindcss_4 watchman] ++ (lib.optional stdenv.isLinux [inotify-tools]);
20
+
21
+
shellHook = ''
22
+
export TAILWINDCSS_PATH="${lib.getExe tailwindcss_4}"
23
+
'';
24
+
});
20
25
};
21
26
}
+28
lexicons/sh/comet/v0/actor/getProfile.json
+28
lexicons/sh/comet/v0/actor/getProfile.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "sh.comet.v0.actor.getProfile",
4
+
"defs": {
5
+
"main": {
6
+
"type": "query",
7
+
"description": "Get the profile view of an actor.",
8
+
"parameters": {
9
+
"type": "params",
10
+
"required": ["actor"],
11
+
"properties": {
12
+
"actor": {
13
+
"type": "string",
14
+
"format": "at-identifier",
15
+
"description": "Handle or DID of account to fetch profile of."
16
+
}
17
+
}
18
+
},
19
+
"output": {
20
+
"encoding": "application/json",
21
+
"schema": {
22
+
"type": "ref",
23
+
"ref": "sh.comet.v0.actor.profile#view"
24
+
}
25
+
}
26
+
}
27
+
}
28
+
}
+37
lexicons/sh/comet/v0/actor/getProfiles.json
+37
lexicons/sh/comet/v0/actor/getProfiles.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "sh.comet.v0.actor.getProfiles",
4
+
"defs": {
5
+
"main": {
6
+
"type": "query",
7
+
"description": "Get the profile views of multiple actors.",
8
+
"parameters": {
9
+
"type": "params",
10
+
"required": ["actors"],
11
+
"properties": {
12
+
"actors": {
13
+
"type": "array",
14
+
"maxLength": 25,
15
+
"items": { "type": "string", "format": "at-identifier" }
16
+
}
17
+
}
18
+
},
19
+
"output": {
20
+
"encoding": "application/json",
21
+
"schema": {
22
+
"type": "object",
23
+
"required": ["profiles"],
24
+
"properties": {
25
+
"profiles": {
26
+
"type": "array",
27
+
"items": {
28
+
"type": "ref",
29
+
"ref": "sh.comet.v0.actor.profile#view"
30
+
}
31
+
}
32
+
}
33
+
}
34
+
}
35
+
}
36
+
}
37
+
}
+109
lexicons/sh/comet/v0/actor/profile.json
+109
lexicons/sh/comet/v0/actor/profile.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "sh.comet.v0.actor.profile",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "A user's Comet profile.",
8
+
"key": "literal:self",
9
+
"record": {
10
+
"type": "object",
11
+
"properties": {
12
+
"displayName": {
13
+
"type": "string",
14
+
"maxGraphemes": 64,
15
+
"maxLength": 640
16
+
},
17
+
"description": {
18
+
"type": "string",
19
+
"description": "Free-form profile description text.",
20
+
"maxGraphemes": 256,
21
+
"maxLength": 2560
22
+
},
23
+
"descriptionFacets": {
24
+
"type": "ref",
25
+
"description": "Annotations of the user's description.",
26
+
"ref": "sh.comet.v0.richtext.facet"
27
+
},
28
+
"avatar": {
29
+
"type": "blob",
30
+
"description": "Small image to be displayed next to posts from account. AKA, 'profile picture'",
31
+
"accept": ["image/png", "image/jpeg"],
32
+
"maxSize": 1000000
33
+
},
34
+
"banner": {
35
+
"type": "blob",
36
+
"description": "Larger horizontal image to display behind profile view.",
37
+
"accept": ["image/png", "image/jpeg"],
38
+
"maxSize": 1000000
39
+
},
40
+
"featuredItems": {
41
+
"type": "array",
42
+
"description": "Pinned items to be shown first on the user's profile.",
43
+
"maxLength": 5,
44
+
"items": { "type": "string", "format": "at-uri" }
45
+
},
46
+
"createdAt": { "type": "string", "format": "datetime" }
47
+
}
48
+
}
49
+
},
50
+
"view": {
51
+
"type": "object",
52
+
"required": ["did", "handle"],
53
+
"properties": {
54
+
"did": { "type": "string", "format": "did" },
55
+
"handle": { "type": "string", "format": "handle" },
56
+
"displayName": {
57
+
"type": "string",
58
+
"maxGraphemes": 64,
59
+
"maxLength": 640
60
+
},
61
+
"avatar": { "type": "string", "format": "uri" },
62
+
"indexedAt": { "type": "string", "format": "datetime" },
63
+
"createdAt": { "type": "string", "format": "datetime" },
64
+
"viewer": { "type": "ref", "ref": "#viewerState" }
65
+
}
66
+
},
67
+
"viewFull": {
68
+
"type": "object",
69
+
"required": ["did", "handle"],
70
+
"properties": {
71
+
"did": { "type": "string", "format": "did" },
72
+
"handle": { "type": "string", "format": "handle" },
73
+
"displayName": {
74
+
"type": "string",
75
+
"maxGraphemes": 64,
76
+
"maxLength": 640
77
+
},
78
+
"description": {
79
+
"type": "string",
80
+
"maxGraphemes": 256,
81
+
"maxLength": 2560
82
+
},
83
+
"descriptionFacets": {
84
+
"type": "ref",
85
+
"ref": "sh.comet.v0.richtext.facet"
86
+
},
87
+
"avatar": { "type": "string", "format": "uri" },
88
+
"banner": { "type": "string", "format": "uri" },
89
+
"followersCount": { "type": "integer" },
90
+
"followsCount": { "type": "integer" },
91
+
"tracksCount": { "type": "integer" },
92
+
"playlistsCount": { "type": "integer" },
93
+
"indexedAt": { "type": "string", "format": "datetime" },
94
+
"createdAt": { "type": "string", "format": "datetime" },
95
+
"featuredItems": {
96
+
"type": "array",
97
+
"maxLength": 5,
98
+
"items": { "type": "string", "format": "at-uri" }
99
+
},
100
+
"viewer": { "type": "ref", "ref": "#viewerState" }
101
+
}
102
+
},
103
+
"viewerState": {
104
+
"type": "object",
105
+
"description": "Metadata about the requesting account's relationship with the user. TODO: determine if we create our own graph or inherit bsky's.",
106
+
"properties": {}
107
+
}
108
+
}
109
+
}
+47
lexicons/sh/comet/v0/feed/comment.json
+47
lexicons/sh/comet/v0/feed/comment.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "sh.comet.v0.feed.comment",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "A comment on a piece of Comet media.",
8
+
"key": "tid",
9
+
"record": {
10
+
"type": "object",
11
+
"required": ["text", "subject", "createdAt"],
12
+
"properties": {
13
+
"text": {
14
+
"type": "string",
15
+
"minLength": 1,
16
+
"minGraphemes": 1,
17
+
"maxLength": 3000,
18
+
"maxGraphemes": 300,
19
+
"description": "The comment's content."
20
+
},
21
+
"subject": {
22
+
"type": "string",
23
+
"format": "at-uri",
24
+
"description": "The piece of media being commented on."
25
+
},
26
+
"reply": {
27
+
"type": "string",
28
+
"format": "at-uri",
29
+
"description": "Another sh.comet.v0.feed.comment this is in reply to."
30
+
},
31
+
"langs": {
32
+
"type": "array",
33
+
"description": "Indicates human language of the comment's text content.",
34
+
"maxLength": 3,
35
+
"items": { "type": "string", "format": "language" }
36
+
},
37
+
"facets": {
38
+
"type": "array",
39
+
"description": "Annotations of text (mentions, URLs, hashtags, etc)",
40
+
"items": { "type": "ref", "ref": "sh.comet.v0.richtext.facet" }
41
+
},
42
+
"createdAt": { "type": "string", "format": "datetime" }
43
+
}
44
+
}
45
+
}
46
+
}
47
+
}
+38
lexicons/sh/comet/v0/feed/defs.json
+38
lexicons/sh/comet/v0/feed/defs.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "sh.comet.v0.feed.defs",
4
+
"defs": {
5
+
"link": {
6
+
"type": "object",
7
+
"description": "Link for the track. Usually to acquire it in some way, e.g. via free download or purchase. | TODO: multiple links?",
8
+
"required": ["type", "value"],
9
+
"properties": {
10
+
"type": {
11
+
"type": "string",
12
+
"knownValues": [
13
+
"sh.comet.v0.feed.defs#downloadLink",
14
+
"sh.comet.v0.feed.defs#buyLink"
15
+
]
16
+
},
17
+
"value": { "type": "string", "format": "uri" }
18
+
}
19
+
},
20
+
"downloadLink": {
21
+
"type": "token",
22
+
"description": "Indicate the link leads to a free download for the track."
23
+
},
24
+
"buyLink": {
25
+
"type": "token",
26
+
"description": "Indicate the link leads to a purchase page for the track."
27
+
},
28
+
"viewerState": {
29
+
"type": "object",
30
+
"description": "Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests.",
31
+
"properties": {
32
+
"like": { "type": "string", "format": "at-uri" },
33
+
"repost": { "type": "string", "format": "at-uri" },
34
+
"featured": { "type": "boolean" }
35
+
}
36
+
}
37
+
}
38
+
}
+44
lexicons/sh/comet/v0/feed/getActorPlaylists.json
+44
lexicons/sh/comet/v0/feed/getActorPlaylists.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "sh.comet.v0.feed.getActorPlaylists",
4
+
"defs": {
5
+
"main": {
6
+
"type": "query",
7
+
"description": "Get a list of an actor's playlists.",
8
+
"parameters": {
9
+
"type": "params",
10
+
"required": ["actor"],
11
+
"properties": {
12
+
"actor": {
13
+
"type": "string",
14
+
"format": "at-identifier"
15
+
},
16
+
"limit": {
17
+
"type": "integer",
18
+
"minimum": 1,
19
+
"maximum": 100,
20
+
"default": 50
21
+
},
22
+
"cursor": { "type": "string" }
23
+
}
24
+
},
25
+
"output": {
26
+
"encoding": "application/json",
27
+
"schema": {
28
+
"type": "object",
29
+
"required": ["playlists"],
30
+
"properties": {
31
+
"cursor": { "type": "string" },
32
+
"playlists": {
33
+
"type": "array",
34
+
"items": {
35
+
"type": "ref",
36
+
"ref": "sh.comet.v0.feed.playlist#view"
37
+
}
38
+
}
39
+
}
40
+
}
41
+
}
42
+
}
43
+
}
44
+
}
+44
lexicons/sh/comet/v0/feed/getActorTracks.json
+44
lexicons/sh/comet/v0/feed/getActorTracks.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "sh.comet.v0.feed.getActorTracks",
4
+
"defs": {
5
+
"main": {
6
+
"type": "query",
7
+
"description": "Get a list of an actor's tracks.",
8
+
"parameters": {
9
+
"type": "params",
10
+
"required": ["actor"],
11
+
"properties": {
12
+
"actor": {
13
+
"type": "string",
14
+
"format": "at-identifier"
15
+
},
16
+
"limit": {
17
+
"type": "integer",
18
+
"minimum": 1,
19
+
"maximum": 100,
20
+
"default": 50
21
+
},
22
+
"cursor": { "type": "string" }
23
+
}
24
+
},
25
+
"output": {
26
+
"encoding": "application/json",
27
+
"schema": {
28
+
"type": "object",
29
+
"required": ["tracks"],
30
+
"properties": {
31
+
"cursor": { "type": "string" },
32
+
"tracks": {
33
+
"type": "array",
34
+
"items": {
35
+
"type": "ref",
36
+
"ref": "sh.comet.v0.feed.track#view"
37
+
}
38
+
}
39
+
}
40
+
}
41
+
}
42
+
}
43
+
}
44
+
}
+19
lexicons/sh/comet/v0/feed/like.json
+19
lexicons/sh/comet/v0/feed/like.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "sh.comet.v0.feed.like",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "Record representing a 'like' of some media. Weakly linked with just an at-uri.",
8
+
"key": "tid",
9
+
"record": {
10
+
"type": "object",
11
+
"required": ["subject", "createdAt"],
12
+
"properties": {
13
+
"subject": { "type": "string", "format": "at-uri" },
14
+
"createdAt": { "type": "string", "format": "datetime" }
15
+
}
16
+
}
17
+
}
18
+
}
19
+
}
+19
lexicons/sh/comet/v0/feed/play.json
+19
lexicons/sh/comet/v0/feed/play.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "sh.comet.v0.feed.play",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "Record representing a 'play' of some media.",
8
+
"key": "tid",
9
+
"record": {
10
+
"type": "object",
11
+
"required": ["subject", "createdAt"],
12
+
"properties": {
13
+
"subject": { "type": "string", "format": "at-uri" },
14
+
"createdAt": { "type": "string", "format": "datetime" }
15
+
}
16
+
}
17
+
}
18
+
}
19
+
}
+114
lexicons/sh/comet/v0/feed/playlist.json
+114
lexicons/sh/comet/v0/feed/playlist.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "sh.comet.v0.feed.playlist",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "A Comet playlist, containing many audio tracks.",
8
+
"key": "tid",
9
+
"record": {
10
+
"type": "object",
11
+
"required": ["title", "type", "createdAt"],
12
+
"properties": {
13
+
"image": {
14
+
"type": "blob",
15
+
"description": "Image to be displayed representing the playlist.",
16
+
"accept": ["image/png", "image/jpeg"],
17
+
"maxSize": 1000000
18
+
},
19
+
"title": {
20
+
"type": "string",
21
+
"description": "Title of the playlist.",
22
+
"minLength": 1,
23
+
"maxLength": 2560,
24
+
"maxGraphemes": 256
25
+
},
26
+
"description": {
27
+
"type": "string",
28
+
"description": "Description of the playlist.",
29
+
"maxLength": 20000,
30
+
"maxGraphemes": 2000
31
+
},
32
+
"descriptionFacets": {
33
+
"type": "ref",
34
+
"description": "Annotations of the playlist's description.",
35
+
"ref": "sh.comet.v0.richtext.facet"
36
+
},
37
+
"type": {
38
+
"type": "string",
39
+
"description": "Type of the playlist. Allows differentiating between playlist's different purposes.",
40
+
"knownValues": [
41
+
"sh.comet.v0.feed.playlist#album",
42
+
"sh.comet.v0.feed.playlist#compilation",
43
+
"sh.comet.v0.feed.playlist#playlist",
44
+
"sh.comet.v0.feed.playlist#podcast"
45
+
]
46
+
},
47
+
"tags": {
48
+
"type": "array",
49
+
"description": "Hashtags for the playlist, usually for genres.",
50
+
"maxLength": 8,
51
+
"items": { "type": "string", "maxLength": 640, "maxGraphemes": 64 }
52
+
},
53
+
"link": {
54
+
"type": "ref",
55
+
"ref": "sh.comet.v0.feed.defs#link"
56
+
},
57
+
"createdAt": {
58
+
"type": "string",
59
+
"format": "datetime",
60
+
"description": "Timestamp for when the playlist was originally created."
61
+
}
62
+
}
63
+
}
64
+
},
65
+
"album": {
66
+
"type": "token",
67
+
"description": "Indicates the playlist is an album or extended play."
68
+
},
69
+
"compilation": {
70
+
"type": "token",
71
+
"description": "Indicates the playlist is a compilation of various tracks, usually put together by a label."
72
+
},
73
+
"playlist": {
74
+
"type": "token",
75
+
"description": "Indicates the playlist is a miscellaneous list of tracks."
76
+
},
77
+
"podcast": {
78
+
"type": "token",
79
+
"description": "Indicates the playlist is a podcast or radio show split into several individual tracks."
80
+
},
81
+
"view": {
82
+
"type": "object",
83
+
"required": ["uri", "cid", "author", "tracks", "record"],
84
+
"properties": {
85
+
"uri": { "type": "string", "format": "at-uri" },
86
+
"cid": { "type": "string", "format": "cid" },
87
+
"author": {
88
+
"type": "ref",
89
+
"ref": "sh.comet.v0.actor.profile#viewFull"
90
+
},
91
+
"tracks": {
92
+
"type": "array",
93
+
"items": { "type": "ref", "ref": "sh.comet.v0.feed.track#view" },
94
+
"description": "TODO: include cursor for pagination if too many?"
95
+
},
96
+
"image": {
97
+
"type": "string",
98
+
"format": "uri",
99
+
"description": "URL pointing to where the image for the playlist can be fetched."
100
+
},
101
+
"record": {
102
+
"type": "ref",
103
+
"ref": "#main"
104
+
},
105
+
"repostCount": { "type": "integer" },
106
+
"likeCount": { "type": "integer" },
107
+
"commentCount": { "type": "integer" },
108
+
"trackCount": { "type": "integer" },
109
+
"indexedAt": { "type": "string", "format": "datetime" },
110
+
"viewer": { "type": "ref", "ref": "sh.comet.v0.feed.defs#viewerState" }
111
+
}
112
+
}
113
+
}
114
+
}
+31
lexicons/sh/comet/v0/feed/playlistTrack.json
+31
lexicons/sh/comet/v0/feed/playlistTrack.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "sh.comet.v0.feed.playlistTrack",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "A link between a Comet track and a playlist.",
8
+
"key": "tid",
9
+
"record": {
10
+
"type": "object",
11
+
"required": ["track", "playlist", "position", "createdAt"],
12
+
"properties": {
13
+
"track": {
14
+
"type": "string",
15
+
"format": "at-uri"
16
+
},
17
+
"playlist": {
18
+
"type": "string",
19
+
"format": "at-uri"
20
+
},
21
+
"position": { "type": "integer", "minimum": 0 },
22
+
"createdAt": {
23
+
"type": "string",
24
+
"format": "datetime",
25
+
"description": "Timestamp for when the track entry was originally added to the playlist."
26
+
}
27
+
}
28
+
}
29
+
}
30
+
}
31
+
}
+19
lexicons/sh/comet/v0/feed/repost.json
+19
lexicons/sh/comet/v0/feed/repost.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "sh.comet.v0.feed.repost",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "Record representing a 'repost' of some media. Weakly linked with just an at-uri.",
8
+
"key": "tid",
9
+
"record": {
10
+
"type": "object",
11
+
"required": ["subject", "createdAt"],
12
+
"properties": {
13
+
"subject": { "type": "string", "format": "at-uri" },
14
+
"createdAt": { "type": "string", "format": "datetime" }
15
+
}
16
+
}
17
+
}
18
+
}
19
+
}
+103
lexicons/sh/comet/v0/feed/track.json
+103
lexicons/sh/comet/v0/feed/track.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "sh.comet.v0.feed.track",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "A Comet audio track. TODO: should probably have some sort of pre-calculated waveform, or have a query to get one from a blob?",
8
+
"key": "tid",
9
+
"record": {
10
+
"type": "object",
11
+
"required": ["audio", "title", "createdAt"],
12
+
"properties": {
13
+
"audio": {
14
+
"type": "blob",
15
+
"description": "Audio of the track, ideally encoded as 96k Opus. Limited to 100mb.",
16
+
"accept": ["audio/ogg"],
17
+
"maxSize": 100000000
18
+
},
19
+
"image": {
20
+
"type": "blob",
21
+
"description": "Image to be displayed representing the track.",
22
+
"accept": ["image/png", "image/jpeg"],
23
+
"maxSize": 1000000
24
+
},
25
+
"title": {
26
+
"type": "string",
27
+
"description": "Title of the track. Usually shouldn't include the creator's name.",
28
+
"minLength": 1,
29
+
"maxLength": 2560,
30
+
"maxGraphemes": 256
31
+
},
32
+
"description": {
33
+
"type": "string",
34
+
"description": "Description of the track.",
35
+
"maxLength": 20000,
36
+
"maxGraphemes": 2000
37
+
},
38
+
"descriptionFacets": {
39
+
"type": "ref",
40
+
"description": "Annotations of the track's description.",
41
+
"ref": "sh.comet.v0.richtext.facet"
42
+
},
43
+
"explicit": {
44
+
"type": "boolean",
45
+
"description": "Whether the track contains explicit content that may objectionable to some people, usually swearing or adult themes."
46
+
},
47
+
"tags": {
48
+
"type": "array",
49
+
"description": "Hashtags for the track, usually for genres.",
50
+
"maxLength": 8,
51
+
"items": { "type": "string", "maxLength": 640, "maxGraphemes": 64 }
52
+
},
53
+
"link": {
54
+
"type": "ref",
55
+
"ref": "sh.comet.v0.feed.defs#link"
56
+
},
57
+
"createdAt": {
58
+
"type": "string",
59
+
"format": "datetime",
60
+
"description": "Timestamp for when the track entry was originally created."
61
+
},
62
+
"releasedAt": {
63
+
"type": "string",
64
+
"format": "datetime",
65
+
"description": "Timestamp for when the track was released. If in the future, may be used to implement pre-savable tracks."
66
+
}
67
+
}
68
+
}
69
+
},
70
+
"view": {
71
+
"type": "object",
72
+
"required": ["uri", "cid", "author", "audio", "record", "indexedAt"],
73
+
"properties": {
74
+
"uri": { "type": "string", "format": "at-uri" },
75
+
"cid": { "type": "string", "format": "cid" },
76
+
"author": {
77
+
"type": "ref",
78
+
"ref": "sh.comet.v0.actor.profile#viewFull"
79
+
},
80
+
"audio": {
81
+
"type": "string",
82
+
"format": "uri",
83
+
"description": "URL pointing to where the audio data for the track can be fetched. May be re-encoded from the original blob."
84
+
},
85
+
"image": {
86
+
"type": "string",
87
+
"format": "uri",
88
+
"description": "URL pointing to where the image for the track can be fetched."
89
+
},
90
+
"record": {
91
+
"type": "ref",
92
+
"ref": "#main"
93
+
},
94
+
"repostCount": { "type": "integer" },
95
+
"likeCount": { "type": "integer" },
96
+
"playCount": { "type": "integer" },
97
+
"commentCount": { "type": "integer" },
98
+
"indexedAt": { "type": "string", "format": "datetime" },
99
+
"viewer": { "type": "ref", "ref": "sh.comet.v0.feed.defs#viewerState" }
100
+
}
101
+
}
102
+
}
103
+
}
+62
lexicons/sh/comet/v0/richtext/facet.json
+62
lexicons/sh/comet/v0/richtext/facet.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "sh.comet.v0.richtext.facet",
4
+
"defs": {
5
+
"main": {
6
+
"type": "object",
7
+
"description": "Annotation of a sub-string within rich text.",
8
+
"required": ["index", "features"],
9
+
"properties": {
10
+
"index": { "type": "ref", "ref": "#byteSlice" },
11
+
"features": {
12
+
"type": "array",
13
+
"items": { "type": "union", "refs": ["#mention", "#link", "#tag"] }
14
+
}
15
+
}
16
+
},
17
+
"mention": {
18
+
"type": "object",
19
+
"description": "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID.",
20
+
"required": ["did"],
21
+
"properties": {
22
+
"did": { "type": "string", "format": "did" }
23
+
}
24
+
},
25
+
"link": {
26
+
"type": "object",
27
+
"description": "Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL.",
28
+
"required": ["uri"],
29
+
"properties": {
30
+
"uri": { "type": "string", "format": "uri" }
31
+
}
32
+
},
33
+
"tag": {
34
+
"type": "object",
35
+
"description": "Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags').",
36
+
"required": ["tag"],
37
+
"properties": {
38
+
"tag": { "type": "string", "maxLength": 640, "maxGraphemes": 64 }
39
+
}
40
+
},
41
+
"timestamp": {
42
+
"type": "object",
43
+
"description": "Facet feature for a timestamp in a track. The text usually is in the format of 'hh:mm:ss' with the hour section being omitted if unnecessary.",
44
+
"properties": {
45
+
"timestamp": {
46
+
"type": "integer",
47
+
"minimum": 0,
48
+
"description": "Reference time, in seconds."
49
+
}
50
+
}
51
+
},
52
+
"byteSlice": {
53
+
"type": "object",
54
+
"description": "Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets.",
55
+
"required": ["byteStart", "byteEnd"],
56
+
"properties": {
57
+
"byteStart": { "type": "integer", "minimum": 0 },
58
+
"byteEnd": { "type": "integer", "minimum": 0 }
59
+
}
60
+
}
61
+
}
62
+
}
+34
lib/comet/application.ex
+34
lib/comet/application.ex
···
1
+
defmodule Comet.Application do
2
+
# See https://hexdocs.pm/elixir/Application.html
3
+
# for more information on OTP Applications
4
+
@moduledoc false
5
+
6
+
use Application
7
+
8
+
@impl true
9
+
def start(_type, _args) do
10
+
children = [
11
+
CometWeb.Telemetry,
12
+
Comet.Repo,
13
+
{DNSCluster, query: Application.get_env(:comet, :dns_cluster_query) || :ignore},
14
+
{Phoenix.PubSub, name: Comet.PubSub},
15
+
# Start a worker by calling: Comet.Worker.start_link(arg)
16
+
# {Comet.Worker, arg},
17
+
# Start to serve requests, typically the last entry
18
+
CometWeb.Endpoint
19
+
]
20
+
21
+
# See https://hexdocs.pm/elixir/Supervisor.html
22
+
# for other strategies and supported options
23
+
opts = [strategy: :one_for_one, name: Comet.Supervisor]
24
+
Supervisor.start_link(children, opts)
25
+
end
26
+
27
+
# Tell Phoenix to update the endpoint configuration
28
+
# whenever the application is updated.
29
+
@impl true
30
+
def config_change(changed, _new, removed) do
31
+
CometWeb.Endpoint.config_change(changed, removed)
32
+
:ok
33
+
end
34
+
end
+3
lib/comet/mailer.ex
+3
lib/comet/mailer.ex
+5
lib/comet/repo.ex
+5
lib/comet/repo.ex
+9
lib/comet.ex
+9
lib/comet.ex
+11
lib/comet_app/components/title.ex
+11
lib/comet_app/components/title.ex
+27
lib/comet_app/layouts/main.ex
+27
lib/comet_app/layouts/main.ex
···
1
+
defmodule CometApp.MainLayout do
2
+
use Hologram.Component
3
+
4
+
alias CometApp.Components.Title
5
+
alias Hologram.UI.Runtime
6
+
7
+
prop :page_title, :string, default: nil
8
+
9
+
def template do
10
+
~HOLO"""
11
+
<!DOCTYPE html>
12
+
<html lang="en">
13
+
<head>
14
+
<meta charset="utf-8" />
15
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
16
+
<link rel="stylesheet" href="/assets/js/app.css" />
17
+
<link rel="stylesheet" href="/assets/css/app.css" />
18
+
<Title text={@page_title} />
19
+
<Runtime />
20
+
</head>
21
+
<body class="from-amber-900 bg-linear-to-tl to-stone-950 to-80% bg-fixed text-white p-10 w-screen h-screen flex justify-center flex-col">
22
+
<slot/>
23
+
</body>
24
+
</html>
25
+
"""
26
+
end
27
+
end
+28
lib/comet_app/pages/home.ex
+28
lib/comet_app/pages/home.ex
···
1
+
defmodule CometApp.HomePage do
2
+
use Hologram.Page
3
+
4
+
route "/"
5
+
6
+
layout CometApp.MainLayout, page_title: "Home"
7
+
8
+
# def init(_params, component, _server) do
9
+
# # In real app, fetch from database
10
+
# posts = [
11
+
# %{id: 1, title: "First Post", excerpt: "This is my first post"},
12
+
# %{id: 2, title: "Second Post", excerpt: nil}
13
+
# ]
14
+
15
+
# put_state(component, :posts, posts)
16
+
# end
17
+
18
+
def template do
19
+
~HOLO"""
20
+
<h1 class="italic text-9xl tracking-tighter leading-30 mb-10 font-thin">
21
+
<span class="font-medium">Comet</span> is the next-generation of music streaming.
22
+
</h1>
23
+
<p class="text-2xl font-light">
24
+
Powered by the AT Protocol, we're putting the power back in the hands of musicians.
25
+
</p>
26
+
"""
27
+
end
28
+
end
+493
lib/comet_web/components/core_components.ex
+493
lib/comet_web/components/core_components.ex
···
1
+
defmodule CometWeb.CoreComponents do
2
+
@moduledoc """
3
+
Provides core UI components.
4
+
5
+
At first glance, this module may seem daunting, but its goal is to provide
6
+
core building blocks for your application, such as tables, forms, and
7
+
inputs. The components consist mostly of markup and are well-documented
8
+
with doc strings and declarative assigns. You may customize and style
9
+
them in any way you want, based on your application growth and needs.
10
+
11
+
The foundation for styling is Tailwind CSS, a utility-first CSS framework. Here are useful references:
12
+
13
+
* [Tailwind CSS](https://tailwindcss.com) - the foundational framework
14
+
we build on. You will use it for layout, sizing, flexbox, grid, and
15
+
spacing.
16
+
17
+
* [Heroicons](https://heroicons.com) - see `icon/1` for usage.
18
+
19
+
* [Phoenix.Component](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html) -
20
+
the component system used by Phoenix. Some components, such as `<.link>`
21
+
and `<.form>`, are defined there.
22
+
23
+
"""
24
+
use Phoenix.Component
25
+
use Gettext, backend: CometWeb.Gettext
26
+
27
+
alias Phoenix.LiveView.JS
28
+
29
+
@doc """
30
+
Renders flash notices.
31
+
32
+
## Examples
33
+
34
+
<.flash kind={:info} flash={@flash} />
35
+
<.flash kind={:info} phx-mounted={show("#flash")}>Welcome Back!</.flash>
36
+
"""
37
+
attr :id, :string, doc: "the optional id of flash container"
38
+
attr :flash, :map, default: %{}, doc: "the map of flash messages to display"
39
+
attr :title, :string, default: nil
40
+
attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup"
41
+
attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container"
42
+
43
+
slot :inner_block, doc: "the optional inner block that renders the flash message"
44
+
45
+
def flash(assigns) do
46
+
assigns = assign_new(assigns, :id, fn -> "flash-#{assigns.kind}" end)
47
+
48
+
~H"""
49
+
<div
50
+
:if={msg = render_slot(@inner_block) || Phoenix.Flash.get(@flash, @kind)}
51
+
id={@id}
52
+
phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("##{@id}")}
53
+
role="alert"
54
+
class="toast toast-top toast-end z-50"
55
+
{@rest}
56
+
>
57
+
<div class={[
58
+
"alert w-80 sm:w-96 max-w-80 sm:max-w-96 text-wrap",
59
+
@kind == :info && "alert-info",
60
+
@kind == :error && "alert-error"
61
+
]}>
62
+
<.icon :if={@kind == :info} name="hero-information-circle" class="size-5 shrink-0" />
63
+
<.icon :if={@kind == :error} name="hero-exclamation-circle" class="size-5 shrink-0" />
64
+
<div>
65
+
<p :if={@title} class="font-semibold">{@title}</p>
66
+
<p>{msg}</p>
67
+
</div>
68
+
<div class="flex-1" />
69
+
<button type="button" class="group self-start cursor-pointer" aria-label={gettext("close")}>
70
+
<.icon name="hero-x-mark" class="size-5 opacity-40 group-hover:opacity-70" />
71
+
</button>
72
+
</div>
73
+
</div>
74
+
"""
75
+
end
76
+
77
+
@doc """
78
+
Renders a button with navigation support.
79
+
80
+
## Examples
81
+
82
+
<.button>Send!</.button>
83
+
<.button phx-click="go" variant="primary">Send!</.button>
84
+
<.button navigate={~p"/"}>Home</.button>
85
+
"""
86
+
attr :rest, :global, include: ~w(href navigate patch method download name value disabled)
87
+
attr :class, :any
88
+
attr :variant, :string, values: ~w(primary)
89
+
slot :inner_block, required: true
90
+
91
+
def button(%{rest: rest} = assigns) do
92
+
variants = %{"primary" => "btn-primary", nil => "btn-primary btn-soft"}
93
+
94
+
assigns =
95
+
assign_new(assigns, :class, fn ->
96
+
["btn", Map.fetch!(variants, assigns[:variant])]
97
+
end)
98
+
99
+
if rest[:href] || rest[:navigate] || rest[:patch] do
100
+
~H"""
101
+
<.link class={@class} {@rest}>
102
+
{render_slot(@inner_block)}
103
+
</.link>
104
+
"""
105
+
else
106
+
~H"""
107
+
<button class={@class} {@rest}>
108
+
{render_slot(@inner_block)}
109
+
</button>
110
+
"""
111
+
end
112
+
end
113
+
114
+
@doc """
115
+
Renders an input with label and error messages.
116
+
117
+
A `Phoenix.HTML.FormField` may be passed as argument,
118
+
which is used to retrieve the input name, id, and values.
119
+
Otherwise all attributes may be passed explicitly.
120
+
121
+
## Types
122
+
123
+
This function accepts all HTML input types, considering that:
124
+
125
+
* You may also set `type="select"` to render a `<select>` tag
126
+
127
+
* `type="checkbox"` is used exclusively to render boolean values
128
+
129
+
* For live file uploads, see `Phoenix.Component.live_file_input/1`
130
+
131
+
See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input
132
+
for more information. Unsupported types, such as radio, are best
133
+
written directly in your templates.
134
+
135
+
## Examples
136
+
137
+
```heex
138
+
<.input field={@form[:email]} type="email" />
139
+
<.input name="my-input" errors={["oh no!"]} />
140
+
```
141
+
142
+
## Select type
143
+
144
+
When using `type="select"`, you must pass the `options` and optionally
145
+
a `value` to mark which option should be preselected.
146
+
147
+
```heex
148
+
<.input field={@form[:user_type]} type="select" options={["Admin": "admin", "User": "user"]} />
149
+
```
150
+
151
+
For more information on what kind of data can be passed to `options` see
152
+
[`options_for_select`](https://hexdocs.pm/phoenix_html/Phoenix.HTML.Form.html#options_for_select/2).
153
+
"""
154
+
attr :id, :any, default: nil
155
+
attr :name, :any
156
+
attr :label, :string, default: nil
157
+
attr :value, :any
158
+
159
+
attr :type, :string,
160
+
default: "text",
161
+
values: ~w(checkbox color date datetime-local email file month number password
162
+
search select tel text textarea time url week hidden)
163
+
164
+
attr :field, Phoenix.HTML.FormField,
165
+
doc: "a form field struct retrieved from the form, for example: @form[:email]"
166
+
167
+
attr :errors, :list, default: []
168
+
attr :checked, :boolean, doc: "the checked flag for checkbox inputs"
169
+
attr :prompt, :string, default: nil, doc: "the prompt for select inputs"
170
+
attr :options, :list, doc: "the options to pass to Phoenix.HTML.Form.options_for_select/2"
171
+
attr :multiple, :boolean, default: false, doc: "the multiple flag for select inputs"
172
+
attr :class, :any, default: nil, doc: "the input class to use over defaults"
173
+
attr :error_class, :any, default: nil, doc: "the input error class to use over defaults"
174
+
175
+
attr :rest, :global,
176
+
include: ~w(accept autocomplete capture cols disabled form list max maxlength min minlength
177
+
multiple pattern placeholder readonly required rows size step)
178
+
179
+
def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
180
+
errors = if Phoenix.Component.used_input?(field), do: field.errors, else: []
181
+
182
+
assigns
183
+
|> assign(field: nil, id: assigns.id || field.id)
184
+
|> assign(:errors, Enum.map(errors, &translate_error(&1)))
185
+
|> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end)
186
+
|> assign_new(:value, fn -> field.value end)
187
+
|> input()
188
+
end
189
+
190
+
def input(%{type: "hidden"} = assigns) do
191
+
~H"""
192
+
<input type="hidden" id={@id} name={@name} value={@value} {@rest} />
193
+
"""
194
+
end
195
+
196
+
def input(%{type: "checkbox"} = assigns) do
197
+
assigns =
198
+
assign_new(assigns, :checked, fn ->
199
+
Phoenix.HTML.Form.normalize_value("checkbox", assigns[:value])
200
+
end)
201
+
202
+
~H"""
203
+
<div class="fieldset mb-2">
204
+
<label>
205
+
<input
206
+
type="hidden"
207
+
name={@name}
208
+
value="false"
209
+
disabled={@rest[:disabled]}
210
+
form={@rest[:form]}
211
+
/>
212
+
<span class="label">
213
+
<input
214
+
type="checkbox"
215
+
id={@id}
216
+
name={@name}
217
+
value="true"
218
+
checked={@checked}
219
+
class={@class || "checkbox checkbox-sm"}
220
+
{@rest}
221
+
/>{@label}
222
+
</span>
223
+
</label>
224
+
<.error :for={msg <- @errors}>{msg}</.error>
225
+
</div>
226
+
"""
227
+
end
228
+
229
+
def input(%{type: "select"} = assigns) do
230
+
~H"""
231
+
<div class="fieldset mb-2">
232
+
<label>
233
+
<span :if={@label} class="label mb-1">{@label}</span>
234
+
<select
235
+
id={@id}
236
+
name={@name}
237
+
class={[@class || "w-full select", @errors != [] && (@error_class || "select-error")]}
238
+
multiple={@multiple}
239
+
{@rest}
240
+
>
241
+
<option :if={@prompt} value="">{@prompt}</option>
242
+
{Phoenix.HTML.Form.options_for_select(@options, @value)}
243
+
</select>
244
+
</label>
245
+
<.error :for={msg <- @errors}>{msg}</.error>
246
+
</div>
247
+
"""
248
+
end
249
+
250
+
def input(%{type: "textarea"} = assigns) do
251
+
~H"""
252
+
<div class="fieldset mb-2">
253
+
<label>
254
+
<span :if={@label} class="label mb-1">{@label}</span>
255
+
<textarea
256
+
id={@id}
257
+
name={@name}
258
+
class={[
259
+
@class || "w-full textarea",
260
+
@errors != [] && (@error_class || "textarea-error")
261
+
]}
262
+
{@rest}
263
+
>{Phoenix.HTML.Form.normalize_value("textarea", @value)}</textarea>
264
+
</label>
265
+
<.error :for={msg <- @errors}>{msg}</.error>
266
+
</div>
267
+
"""
268
+
end
269
+
270
+
# All other inputs text, datetime-local, url, password, etc. are handled here...
271
+
def input(assigns) do
272
+
~H"""
273
+
<div class="fieldset mb-2">
274
+
<label>
275
+
<span :if={@label} class="label mb-1">{@label}</span>
276
+
<input
277
+
type={@type}
278
+
name={@name}
279
+
id={@id}
280
+
value={Phoenix.HTML.Form.normalize_value(@type, @value)}
281
+
class={[
282
+
@class || "w-full input",
283
+
@errors != [] && (@error_class || "input-error")
284
+
]}
285
+
{@rest}
286
+
/>
287
+
</label>
288
+
<.error :for={msg <- @errors}>{msg}</.error>
289
+
</div>
290
+
"""
291
+
end
292
+
293
+
# Helper used by inputs to generate form errors
294
+
defp error(assigns) do
295
+
~H"""
296
+
<p class="mt-1.5 flex gap-2 items-center text-sm text-error">
297
+
<.icon name="hero-exclamation-circle" class="size-5" />
298
+
{render_slot(@inner_block)}
299
+
</p>
300
+
"""
301
+
end
302
+
303
+
@doc """
304
+
Renders a header with title.
305
+
"""
306
+
slot :inner_block, required: true
307
+
slot :subtitle
308
+
slot :actions
309
+
310
+
def header(assigns) do
311
+
~H"""
312
+
<header class={[@actions != [] && "flex items-center justify-between gap-6", "pb-4"]}>
313
+
<div>
314
+
<h1 class="text-lg font-semibold leading-8">
315
+
{render_slot(@inner_block)}
316
+
</h1>
317
+
<p :if={@subtitle != []} class="text-sm text-base-content/70">
318
+
{render_slot(@subtitle)}
319
+
</p>
320
+
</div>
321
+
<div class="flex-none">{render_slot(@actions)}</div>
322
+
</header>
323
+
"""
324
+
end
325
+
326
+
@doc """
327
+
Renders a table with generic styling.
328
+
329
+
## Examples
330
+
331
+
<.table id="users" rows={@users}>
332
+
<:col :let={user} label="id">{user.id}</:col>
333
+
<:col :let={user} label="username">{user.username}</:col>
334
+
</.table>
335
+
"""
336
+
attr :id, :string, required: true
337
+
attr :rows, :list, required: true
338
+
attr :row_id, :any, default: nil, doc: "the function for generating the row id"
339
+
attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row"
340
+
341
+
attr :row_item, :any,
342
+
default: &Function.identity/1,
343
+
doc: "the function for mapping each row before calling the :col and :action slots"
344
+
345
+
slot :col, required: true do
346
+
attr :label, :string
347
+
end
348
+
349
+
slot :action, doc: "the slot for showing user actions in the last table column"
350
+
351
+
def table(assigns) do
352
+
assigns =
353
+
with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do
354
+
assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end)
355
+
end
356
+
357
+
~H"""
358
+
<table class="table table-zebra">
359
+
<thead>
360
+
<tr>
361
+
<th :for={col <- @col}>{col[:label]}</th>
362
+
<th :if={@action != []}>
363
+
<span class="sr-only">{gettext("Actions")}</span>
364
+
</th>
365
+
</tr>
366
+
</thead>
367
+
<tbody id={@id} phx-update={is_struct(@rows, Phoenix.LiveView.LiveStream) && "stream"}>
368
+
<tr :for={row <- @rows} id={@row_id && @row_id.(row)}>
369
+
<td
370
+
:for={col <- @col}
371
+
phx-click={@row_click && @row_click.(row)}
372
+
class={@row_click && "hover:cursor-pointer"}
373
+
>
374
+
{render_slot(col, @row_item.(row))}
375
+
</td>
376
+
<td :if={@action != []} class="w-0 font-semibold">
377
+
<div class="flex gap-4">
378
+
<%= for action <- @action do %>
379
+
{render_slot(action, @row_item.(row))}
380
+
<% end %>
381
+
</div>
382
+
</td>
383
+
</tr>
384
+
</tbody>
385
+
</table>
386
+
"""
387
+
end
388
+
389
+
@doc """
390
+
Renders a data list.
391
+
392
+
## Examples
393
+
394
+
<.list>
395
+
<:item title="Title">{@post.title}</:item>
396
+
<:item title="Views">{@post.views}</:item>
397
+
</.list>
398
+
"""
399
+
slot :item, required: true do
400
+
attr :title, :string, required: true
401
+
end
402
+
403
+
def list(assigns) do
404
+
~H"""
405
+
<ul class="list">
406
+
<li :for={item <- @item} class="list-row">
407
+
<div class="list-col-grow">
408
+
<div class="font-bold">{item.title}</div>
409
+
<div>{render_slot(item)}</div>
410
+
</div>
411
+
</li>
412
+
</ul>
413
+
"""
414
+
end
415
+
416
+
@doc """
417
+
Renders a [Heroicon](https://heroicons.com).
418
+
419
+
Heroicons come in three styles โ outline, solid, and mini.
420
+
By default, the outline style is used, but solid and mini may
421
+
be applied by using the `-solid` and `-mini` suffix.
422
+
423
+
You can customize the size and colors of the icons by setting
424
+
width, height, and background color classes.
425
+
426
+
Icons are extracted from the `deps/heroicons` directory and bundled within
427
+
your compiled app.css by the plugin in `assets/vendor/heroicons.js`.
428
+
429
+
## Examples
430
+
431
+
<.icon name="hero-x-mark" />
432
+
<.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" />
433
+
"""
434
+
attr :name, :string, required: true
435
+
attr :class, :any, default: "size-4"
436
+
437
+
def icon(%{name: "hero-" <> _} = assigns) do
438
+
~H"""
439
+
<span class={[@name, @class]} />
440
+
"""
441
+
end
442
+
443
+
## JS Commands
444
+
445
+
def show(js \\ %JS{}, selector) do
446
+
JS.show(js,
447
+
to: selector,
448
+
time: 300,
449
+
transition:
450
+
{"transition-all ease-out duration-300",
451
+
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95",
452
+
"opacity-100 translate-y-0 sm:scale-100"}
453
+
)
454
+
end
455
+
456
+
def hide(js \\ %JS{}, selector) do
457
+
JS.hide(js,
458
+
to: selector,
459
+
time: 200,
460
+
transition:
461
+
{"transition-all ease-in duration-200", "opacity-100 translate-y-0 sm:scale-100",
462
+
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"}
463
+
)
464
+
end
465
+
466
+
@doc """
467
+
Translates an error message using gettext.
468
+
"""
469
+
def translate_error({msg, opts}) do
470
+
# When using gettext, we typically pass the strings we want
471
+
# to translate as a static argument:
472
+
#
473
+
# # Translate the number of files with plural rules
474
+
# dngettext("errors", "1 file", "%{count} files", count)
475
+
#
476
+
# However the error messages in our forms and APIs are generated
477
+
# dynamically, so we need to translate them by calling Gettext
478
+
# with our gettext backend as first argument. Translations are
479
+
# available in the errors.po file (as we use the "errors" domain).
480
+
if count = opts[:count] do
481
+
Gettext.dngettext(CometWeb.Gettext, "errors", msg, msg, count, opts)
482
+
else
483
+
Gettext.dgettext(CometWeb.Gettext, "errors", msg, opts)
484
+
end
485
+
end
486
+
487
+
@doc """
488
+
Translates the errors for a field from a keyword list of errors.
489
+
"""
490
+
def translate_errors(errors, field) when is_list(errors) do
491
+
for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts})
492
+
end
493
+
end
+37
lib/comet_web/components/layouts/root.html.heex
+37
lib/comet_web/components/layouts/root.html.heex
···
1
+
<!DOCTYPE html>
2
+
<html lang="en">
3
+
<head>
4
+
<meta charset="utf-8" />
5
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6
+
<meta name="csrf-token" content={get_csrf_token()} />
7
+
<.live_title default="Comet" suffix=" ยท Phoenix Framework">
8
+
{assigns[:page_title]}
9
+
</.live_title>
10
+
<link phx-track-static rel="stylesheet" href={~p"/assets/js/app.css"} />
11
+
<link phx-track-static rel="stylesheet" href={~p"/assets/css/app.css"} />
12
+
<script defer phx-track-static type="text/javascript" src={~p"/assets/js/app.js"}>
13
+
</script>
14
+
<script>
15
+
(() => {
16
+
const setTheme = (theme) => {
17
+
if (theme === "system") {
18
+
localStorage.removeItem("phx:theme");
19
+
document.documentElement.removeAttribute("data-theme");
20
+
} else {
21
+
localStorage.setItem("phx:theme", theme);
22
+
document.documentElement.setAttribute("data-theme", theme);
23
+
}
24
+
};
25
+
if (!document.documentElement.hasAttribute("data-theme")) {
26
+
setTheme(localStorage.getItem("phx:theme") || "system");
27
+
}
28
+
window.addEventListener("storage", (e) => e.key === "phx:theme" && setTheme(e.newValue || "system"));
29
+
30
+
window.addEventListener("phx:set-theme", (e) => setTheme(e.target.dataset.phxTheme));
31
+
})();
32
+
</script>
33
+
</head>
34
+
<body>
35
+
{@inner_content}
36
+
</body>
37
+
</html>
+154
lib/comet_web/components/layouts.ex
+154
lib/comet_web/components/layouts.ex
···
1
+
defmodule CometWeb.Layouts do
2
+
@moduledoc """
3
+
This module holds layouts and related functionality
4
+
used by your application.
5
+
"""
6
+
use CometWeb, :html
7
+
8
+
# Embed all files in layouts/* within this module.
9
+
# The default root.html.heex file contains the HTML
10
+
# skeleton of your application, namely HTML headers
11
+
# and other static content.
12
+
embed_templates "layouts/*"
13
+
14
+
@doc """
15
+
Renders your app layout.
16
+
17
+
This function is typically invoked from every template,
18
+
and it often contains your application menu, sidebar,
19
+
or similar.
20
+
21
+
## Examples
22
+
23
+
<Layouts.app flash={@flash}>
24
+
<h1>Content</h1>
25
+
</Layouts.app>
26
+
27
+
"""
28
+
attr :flash, :map, required: true, doc: "the map of flash messages"
29
+
30
+
attr :current_scope, :map,
31
+
default: nil,
32
+
doc: "the current [scope](https://hexdocs.pm/phoenix/scopes.html)"
33
+
34
+
slot :inner_block, required: true
35
+
36
+
def app(assigns) do
37
+
~H"""
38
+
<header class="navbar px-4 sm:px-6 lg:px-8">
39
+
<div class="flex-1">
40
+
<a href="/" class="flex-1 flex w-fit items-center gap-2">
41
+
<img src={~p"/images/logo.svg"} width="36" />
42
+
<span class="text-sm font-semibold">v{Application.spec(:phoenix, :vsn)}</span>
43
+
</a>
44
+
</div>
45
+
<div class="flex-none">
46
+
<ul class="flex flex-column px-1 space-x-4 items-center">
47
+
<li>
48
+
<a href="https://phoenixframework.org/" class="btn btn-ghost">Website</a>
49
+
</li>
50
+
<li>
51
+
<a href="https://github.com/phoenixframework/phoenix" class="btn btn-ghost">GitHub</a>
52
+
</li>
53
+
<li>
54
+
<.theme_toggle />
55
+
</li>
56
+
<li>
57
+
<a href="https://hexdocs.pm/phoenix/overview.html" class="btn btn-primary">
58
+
Get Started <span aria-hidden="true">→</span>
59
+
</a>
60
+
</li>
61
+
</ul>
62
+
</div>
63
+
</header>
64
+
65
+
<main class="px-4 py-20 sm:px-6 lg:px-8">
66
+
<div class="mx-auto max-w-2xl space-y-4">
67
+
{render_slot(@inner_block)}
68
+
</div>
69
+
</main>
70
+
71
+
<.flash_group flash={@flash} />
72
+
"""
73
+
end
74
+
75
+
@doc """
76
+
Shows the flash group with standard titles and content.
77
+
78
+
## Examples
79
+
80
+
<.flash_group flash={@flash} />
81
+
"""
82
+
attr :flash, :map, required: true, doc: "the map of flash messages"
83
+
attr :id, :string, default: "flash-group", doc: "the optional id of flash container"
84
+
85
+
def flash_group(assigns) do
86
+
~H"""
87
+
<div id={@id} aria-live="polite">
88
+
<.flash kind={:info} flash={@flash} />
89
+
<.flash kind={:error} flash={@flash} />
90
+
91
+
<.flash
92
+
id="client-error"
93
+
kind={:error}
94
+
title={gettext("We can't find the internet")}
95
+
phx-disconnected={show(".phx-client-error #client-error") |> JS.remove_attribute("hidden")}
96
+
phx-connected={hide("#client-error") |> JS.set_attribute({"hidden", ""})}
97
+
hidden
98
+
>
99
+
{gettext("Attempting to reconnect")}
100
+
<.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" />
101
+
</.flash>
102
+
103
+
<.flash
104
+
id="server-error"
105
+
kind={:error}
106
+
title={gettext("Something went wrong!")}
107
+
phx-disconnected={show(".phx-server-error #server-error") |> JS.remove_attribute("hidden")}
108
+
phx-connected={hide("#server-error") |> JS.set_attribute({"hidden", ""})}
109
+
hidden
110
+
>
111
+
{gettext("Attempting to reconnect")}
112
+
<.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" />
113
+
</.flash>
114
+
</div>
115
+
"""
116
+
end
117
+
118
+
@doc """
119
+
Provides dark vs light theme toggle based on themes defined in app.css.
120
+
121
+
See <head> in root.html.heex which applies the theme before page load.
122
+
"""
123
+
def theme_toggle(assigns) do
124
+
~H"""
125
+
<div class="card relative flex flex-row items-center border-2 border-base-300 bg-base-300 rounded-full">
126
+
<div class="absolute w-1/3 h-full rounded-full border-1 border-base-200 bg-base-100 brightness-200 left-0 [[data-theme=light]_&]:left-1/3 [[data-theme=dark]_&]:left-2/3 transition-[left]" />
127
+
128
+
<button
129
+
class="flex p-2 cursor-pointer w-1/3"
130
+
phx-click={JS.dispatch("phx:set-theme")}
131
+
data-phx-theme="system"
132
+
>
133
+
<.icon name="hero-computer-desktop-micro" class="size-4 opacity-75 hover:opacity-100" />
134
+
</button>
135
+
136
+
<button
137
+
class="flex p-2 cursor-pointer w-1/3"
138
+
phx-click={JS.dispatch("phx:set-theme")}
139
+
data-phx-theme="light"
140
+
>
141
+
<.icon name="hero-sun-micro" class="size-4 opacity-75 hover:opacity-100" />
142
+
</button>
143
+
144
+
<button
145
+
class="flex p-2 cursor-pointer w-1/3"
146
+
phx-click={JS.dispatch("phx:set-theme")}
147
+
data-phx-theme="dark"
148
+
>
149
+
<.icon name="hero-moon-micro" class="size-4 opacity-75 hover:opacity-100" />
150
+
</button>
151
+
</div>
152
+
"""
153
+
end
154
+
end
+24
lib/comet_web/controllers/error_html.ex
+24
lib/comet_web/controllers/error_html.ex
···
1
+
defmodule CometWeb.ErrorHTML do
2
+
@moduledoc """
3
+
This module is invoked by your endpoint in case of errors on HTML requests.
4
+
5
+
See config/config.exs.
6
+
"""
7
+
use CometWeb, :html
8
+
9
+
# If you want to customize your error pages,
10
+
# uncomment the embed_templates/1 call below
11
+
# and add pages to the error directory:
12
+
#
13
+
# * lib/comet_web/controllers/error_html/404.html.heex
14
+
# * lib/comet_web/controllers/error_html/500.html.heex
15
+
#
16
+
# embed_templates "error_html/*"
17
+
18
+
# The default is to render a plain text page based on
19
+
# the template name. For example, "404.html" becomes
20
+
# "Not Found".
21
+
def render(template, _assigns) do
22
+
Phoenix.Controller.status_message_from_template(template)
23
+
end
24
+
end
+21
lib/comet_web/controllers/error_json.ex
+21
lib/comet_web/controllers/error_json.ex
···
1
+
defmodule CometWeb.ErrorJSON do
2
+
@moduledoc """
3
+
This module is invoked by your endpoint in case of errors on JSON requests.
4
+
5
+
See config/config.exs.
6
+
"""
7
+
8
+
# If you want to customize a particular status code,
9
+
# you may add your own clauses, such as:
10
+
#
11
+
# def render("500.json", _assigns) do
12
+
# %{errors: %{detail: "Internal Server Error"}}
13
+
# end
14
+
15
+
# By default, Phoenix returns the status message from
16
+
# the template name. For example, "404.json" becomes
17
+
# "Not Found".
18
+
def render(template, _assigns) do
19
+
%{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}}
20
+
end
21
+
end
+9
lib/comet_web/controllers/page_controller.ex
+9
lib/comet_web/controllers/page_controller.ex
+202
lib/comet_web/controllers/page_html/home.html.heex
+202
lib/comet_web/controllers/page_html/home.html.heex
···
1
+
<Layouts.flash_group flash={@flash} />
2
+
<div class="left-[40rem] fixed inset-y-0 right-0 z-0 hidden lg:block xl:left-[50rem]">
3
+
<svg
4
+
viewBox="0 0 1480 957"
5
+
fill="none"
6
+
aria-hidden="true"
7
+
class="absolute inset-0 h-full w-full"
8
+
preserveAspectRatio="xMinYMid slice"
9
+
>
10
+
<path fill="#EE7868" d="M0 0h1480v957H0z" />
11
+
<path
12
+
d="M137.542 466.27c-582.851-48.41-988.806-82.127-1608.412 658.2l67.39 810 3083.15-256.51L1535.94-49.622l-98.36 8.183C1269.29 281.468 734.115 515.799 146.47 467.012l-8.928-.742Z"
13
+
fill="#FF9F92"
14
+
/>
15
+
<path
16
+
d="M371.028 528.664C-169.369 304.988-545.754 149.198-1361.45 665.565l-182.58 792.025 3014.73 694.98 389.42-1689.25-96.18-22.171C1505.28 697.438 924.153 757.586 379.305 532.09l-8.277-3.426Z"
17
+
fill="#FA8372"
18
+
/>
19
+
<path
20
+
d="M359.326 571.714C-104.765 215.795-428.003-32.102-1349.55 255.554l-282.3 1224.596 3047.04 722.01 312.24-1354.467C1411.25 1028.3 834.355 935.995 366.435 577.166l-7.109-5.452Z"
21
+
fill="#E96856"
22
+
fill-opacity=".6"
23
+
/>
24
+
<path
25
+
d="M1593.87 1236.88c-352.15 92.63-885.498-145.85-1244.602-613.557l-5.455-7.105C-12.347 152.31-260.41-170.8-1225-131.458l-368.63 1599.048 3057.19 704.76 130.31-935.47Z"
26
+
fill="#C42652"
27
+
fill-opacity=".2"
28
+
/>
29
+
<path
30
+
d="M1411.91 1526.93c-363.79 15.71-834.312-330.6-1085.883-863.909l-3.822-8.102C72.704 125.95-101.074-242.476-1052.01-408.907l-699.85 1484.267 2837.75 1338.01 326.02-886.44Z"
31
+
fill="#A41C42"
32
+
fill-opacity=".2"
33
+
/>
34
+
<path
35
+
d="M1116.26 1863.69c-355.457-78.98-720.318-535.27-825.287-1115.521l-1.594-8.816C185.286 163.833 112.786-237.016-762.678-643.898L-1822.83 608.665 571.922 2635.55l544.338-771.86Z"
36
+
fill="#A41C42"
37
+
fill-opacity=".2"
38
+
/>
39
+
</svg>
40
+
</div>
41
+
<div class="px-4 py-10 sm:px-6 sm:py-28 lg:px-8 xl:px-28 xl:py-32">
42
+
<div class="mx-auto max-w-xl lg:mx-0">
43
+
<svg viewBox="0 0 71 48" class="h-12" aria-hidden="true">
44
+
<path
45
+
d="m26.371 33.477-.552-.1c-3.92-.729-6.397-3.1-7.57-6.829-.733-2.324.597-4.035 3.035-4.148 1.995-.092 3.362 1.055 4.57 2.39 1.557 1.72 2.984 3.558 4.514 5.305 2.202 2.515 4.797 4.134 8.347 3.634 3.183-.448 5.958-1.725 8.371-3.828.363-.316.761-.592 1.144-.886l-.241-.284c-2.027.63-4.093.841-6.205.735-3.195-.16-6.24-.828-8.964-2.582-2.486-1.601-4.319-3.746-5.19-6.611-.704-2.315.736-3.934 3.135-3.6.948.133 1.746.56 2.463 1.165.583.493 1.143 1.015 1.738 1.493 2.8 2.25 6.712 2.375 10.265-.068-5.842-.026-9.817-3.24-13.308-7.313-1.366-1.594-2.7-3.216-4.095-4.785-2.698-3.036-5.692-5.71-9.79-6.623C12.8-.623 7.745.14 2.893 2.361 1.926 2.804.997 3.319 0 4.149c.494 0 .763.006 1.032 0 2.446-.064 4.28 1.023 5.602 3.024.962 1.457 1.415 3.104 1.761 4.798.513 2.515.247 5.078.544 7.605.761 6.494 4.08 11.026 10.26 13.346 2.267.852 4.591 1.135 7.172.555ZM10.751 3.852c-.976.246-1.756-.148-2.56-.962 1.377-.343 2.592-.476 3.897-.528-.107.848-.607 1.306-1.336 1.49Zm32.002 37.924c-.085-.626-.62-.901-1.04-1.228-1.857-1.446-4.03-1.958-6.333-2-1.375-.026-2.735-.128-4.031-.61-.595-.22-1.26-.505-1.244-1.272.015-.78.693-1 1.31-1.184.505-.15 1.026-.247 1.6-.382-1.46-.936-2.886-1.065-4.787-.3-2.993 1.202-5.943 1.06-8.926-.017-1.684-.608-3.179-1.563-4.735-2.408l-.043.03a2.96 2.96 0 0 0 .04-.029c-.038-.117-.107-.12-.197-.054l.122.107c1.29 2.115 3.034 3.817 5.004 5.271 3.793 2.8 7.936 4.471 12.784 3.73A66.714 66.714 0 0 1 37 40.877c1.98-.16 3.866.398 5.753.899Zm-9.14-30.345c-.105-.076-.206-.266-.42-.069 1.745 2.36 3.985 4.098 6.683 5.193 4.354 1.767 8.773 2.07 13.293.51 3.51-1.21 6.033-.028 7.343 3.38.19-3.955-2.137-6.837-5.843-7.401-2.084-.318-4.01.373-5.962.94-5.434 1.575-10.485.798-15.094-2.553Zm27.085 15.425c.708.059 1.416.123 2.124.185-1.6-1.405-3.55-1.517-5.523-1.404-3.003.17-5.167 1.903-7.14 3.972-1.739 1.824-3.31 3.87-5.903 4.604.043.078.054.117.066.117.35.005.699.021 1.047.005 3.768-.17 7.317-.965 10.14-3.7.89-.86 1.685-1.817 2.544-2.71.716-.746 1.584-1.159 2.645-1.07Zm-8.753-4.67c-2.812.246-5.254 1.409-7.548 2.943-1.766 1.18-3.654 1.738-5.776 1.37-.374-.066-.75-.114-1.124-.17l-.013.156c.135.07.265.151.405.207.354.14.702.308 1.07.395 4.083.971 7.992.474 11.516-1.803 2.221-1.435 4.521-1.707 7.013-1.336.252.038.503.083.756.107.234.022.479.255.795.003-2.179-1.574-4.526-2.096-7.094-1.872Zm-10.049-9.544c1.475.051 2.943-.142 4.486-1.059-.452.04-.643.04-.827.076-2.126.424-4.033-.04-5.733-1.383-.623-.493-1.257-.974-1.889-1.457-2.503-1.914-5.374-2.555-8.514-2.5.05.154.054.26.108.315 3.417 3.455 7.371 5.836 12.369 6.008Zm24.727 17.731c-2.114-2.097-4.952-2.367-7.578-.537 1.738.078 3.043.632 4.101 1.728.374.388.763.768 1.182 1.106 1.6 1.29 4.311 1.352 5.896.155-1.861-.726-1.861-.726-3.601-2.452Zm-21.058 16.06c-1.858-3.46-4.981-4.24-8.59-4.008a9.667 9.667 0 0 1 2.977 1.39c.84.586 1.547 1.311 2.243 2.055 1.38 1.473 3.534 2.376 4.962 2.07-.656-.412-1.238-.848-1.592-1.507Zm17.29-19.32c0-.023.001-.045.003-.068l-.006.006.006-.006-.036-.004.021.018.012.053Zm-20 14.744a7.61 7.61 0 0 0-.072-.041.127.127 0 0 0 .015.043c.005.008.038 0 .058-.002Zm-.072-.041-.008-.034-.008.01.008-.01-.022-.006.005.026.024.014Z"
46
+
fill="#FD4F00"
47
+
/>
48
+
</svg>
49
+
<div class="mt-10 flex justify-between items-center">
50
+
<h1 class="flex items-center text-sm font-semibold leading-6">
51
+
Phoenix Framework
52
+
<small class="badge badge-warning badge-sm ml-3">
53
+
v{Application.spec(:phoenix, :vsn)}
54
+
</small>
55
+
</h1>
56
+
<Layouts.theme_toggle />
57
+
</div>
58
+
59
+
<p class="text-[2rem] mt-4 font-semibold leading-10 tracking-tighter text-balance">
60
+
Peace of mind from prototype to production.
61
+
</p>
62
+
<p class="mt-4 leading-7 text-base-content/70">
63
+
Build rich, interactive web applications quickly, with less code and fewer moving parts. Join our growing community of developers using Phoenix to craft APIs, HTML5 apps and more, for fun or at scale.
64
+
</p>
65
+
<div class="flex">
66
+
<div class="w-full sm:w-auto">
67
+
<div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-3">
68
+
<a
69
+
href="https://hexdocs.pm/phoenix/overview.html"
70
+
class="group relative rounded-box px-6 py-4 text-sm font-semibold leading-6 sm:py-6"
71
+
>
72
+
<span class="absolute inset-0 rounded-box bg-base-200 transition group-hover:bg-base-300 sm:group-hover:scale-105">
73
+
</span>
74
+
<span class="relative flex items-center gap-4 sm:flex-col">
75
+
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-6 w-6">
76
+
<path d="m12 4 10-2v18l-10 2V4Z" fill="currentColor" fill-opacity=".15" />
77
+
<path
78
+
d="M12 4 2 2v18l10 2m0-18v18m0-18 10-2v18l-10 2"
79
+
stroke="currentColor"
80
+
stroke-width="2"
81
+
stroke-linecap="round"
82
+
stroke-linejoin="round"
83
+
/>
84
+
</svg>
85
+
Guides & Docs
86
+
</span>
87
+
</a>
88
+
<a
89
+
href="https://github.com/phoenixframework/phoenix"
90
+
class="group relative rounded-box px-6 py-4 text-sm font-semibold leading-6 sm:py-6"
91
+
>
92
+
<span class="absolute inset-0 rounded-box bg-base-200 transition group-hover:bg-base-300 sm:group-hover:scale-105">
93
+
</span>
94
+
<span class="relative flex items-center gap-4 sm:flex-col">
95
+
<svg viewBox="0 0 24 24" aria-hidden="true" class="h-6 w-6">
96
+
<path
97
+
fill="currentColor"
98
+
fill-rule="evenodd"
99
+
clip-rule="evenodd"
100
+
d="M12 0C5.37 0 0 5.506 0 12.303c0 5.445 3.435 10.043 8.205 11.674.6.107.825-.262.825-.585 0-.292-.015-1.261-.015-2.291C6 21.67 5.22 20.346 4.98 19.654c-.135-.354-.72-1.446-1.23-1.738-.42-.23-1.02-.8-.015-.815.945-.015 1.62.892 1.845 1.261 1.08 1.86 2.805 1.338 3.495 1.015.105-.8.42-1.338.765-1.645-2.67-.308-5.46-1.37-5.46-6.075 0-1.338.465-2.446 1.23-3.307-.12-.308-.54-1.569.12-3.26 0 0 1.005-.323 3.3 1.26.96-.276 1.98-.415 3-.415s2.04.139 3 .416c2.295-1.6 3.3-1.261 3.3-1.261.66 1.691.24 2.952.12 3.26.765.861 1.23 1.953 1.23 3.307 0 4.721-2.805 5.767-5.475 6.075.435.384.81 1.122.81 2.276 0 1.645-.015 2.968-.015 3.383 0 .323.225.707.825.585a12.047 12.047 0 0 0 5.919-4.489A12.536 12.536 0 0 0 24 12.304C24 5.505 18.63 0 12 0Z"
101
+
/>
102
+
</svg>
103
+
Source Code
104
+
</span>
105
+
</a>
106
+
<a
107
+
href={"https://github.com/phoenixframework/phoenix/blob/v#{Application.spec(:phoenix, :vsn)}/CHANGELOG.md"}
108
+
class="group relative rounded-box px-6 py-4 text-sm font-semibold leading-6 sm:py-6"
109
+
>
110
+
<span class="absolute inset-0 rounded-box bg-base-200 transition group-hover:bg-base-300 sm:group-hover:scale-105">
111
+
</span>
112
+
<span class="relative flex items-center gap-4 sm:flex-col">
113
+
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-6 w-6">
114
+
<path
115
+
d="M12 1v6M12 17v6"
116
+
stroke="currentColor"
117
+
stroke-width="2"
118
+
stroke-linecap="round"
119
+
stroke-linejoin="round"
120
+
/>
121
+
<circle
122
+
cx="12"
123
+
cy="12"
124
+
r="4"
125
+
fill="currentColor"
126
+
fill-opacity=".15"
127
+
stroke="currentColor"
128
+
stroke-width="2"
129
+
stroke-linecap="round"
130
+
stroke-linejoin="round"
131
+
/>
132
+
</svg>
133
+
Changelog
134
+
</span>
135
+
</a>
136
+
</div>
137
+
<div class="mt-10 grid grid-cols-1 gap-y-4 text-sm leading-6 text-base-content/80 sm:grid-cols-2">
138
+
<div>
139
+
<a
140
+
href="https://elixirforum.com"
141
+
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-base-200 hover:text-base-content"
142
+
>
143
+
<svg
144
+
viewBox="0 0 16 16"
145
+
aria-hidden="true"
146
+
class="h-4 w-4 fill-base-content/40 group-hover:fill-base-content"
147
+
>
148
+
<path d="M8 13.833c3.866 0 7-2.873 7-6.416C15 3.873 11.866 1 8 1S1 3.873 1 7.417c0 1.081.292 2.1.808 2.995.606 1.05.806 2.399.086 3.375l-.208.283c-.285.386-.01.905.465.85.852-.098 2.048-.318 3.137-.81a3.717 3.717 0 0 1 1.91-.318c.263.027.53.041.802.041Z" />
149
+
</svg>
150
+
Discuss on the Elixir Forum
151
+
</a>
152
+
</div>
153
+
<div>
154
+
<a
155
+
href="https://discord.gg/elixir"
156
+
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-base-200 hover:text-base-content"
157
+
>
158
+
<svg
159
+
viewBox="0 0 16 16"
160
+
aria-hidden="true"
161
+
class="h-4 w-4 fill-base-content/40 group-hover:fill-base-content"
162
+
>
163
+
<path d="M13.545 2.995c-1.02-.46-2.114-.8-3.257-.994a.05.05 0 0 0-.052.024c-.141.246-.297.567-.406.82a12.377 12.377 0 0 0-3.658 0 8.238 8.238 0 0 0-.412-.82.052.052 0 0 0-.052-.024 13.315 13.315 0 0 0-3.257.994.046.046 0 0 0-.021.018C.356 6.063-.213 9.036.066 11.973c.001.015.01.029.02.038a13.353 13.353 0 0 0 3.996 1.987.052.052 0 0 0 .056-.018c.308-.414.582-.85.818-1.309a.05.05 0 0 0-.028-.069 8.808 8.808 0 0 1-1.248-.585.05.05 0 0 1-.005-.084c.084-.062.168-.126.248-.191a.05.05 0 0 1 .051-.007c2.619 1.176 5.454 1.176 8.041 0a.05.05 0 0 1 .053.006c.08.065.164.13.248.192a.05.05 0 0 1-.004.084c-.399.23-.813.423-1.249.585a.05.05 0 0 0-.027.07c.24.457.514.893.817 1.307a.051.051 0 0 0 .056.019 13.31 13.31 0 0 0 4.001-1.987.05.05 0 0 0 .021-.037c.334-3.396-.559-6.345-2.365-8.96a.04.04 0 0 0-.021-.02Zm-8.198 7.19c-.789 0-1.438-.712-1.438-1.587 0-.874.637-1.586 1.438-1.586.807 0 1.45.718 1.438 1.586 0 .875-.637 1.587-1.438 1.587Zm5.316 0c-.788 0-1.438-.712-1.438-1.587 0-.874.637-1.586 1.438-1.586.807 0 1.45.718 1.438 1.586 0 .875-.63 1.587-1.438 1.587Z" />
164
+
</svg>
165
+
Join our Discord server
166
+
</a>
167
+
</div>
168
+
<div>
169
+
<a
170
+
href="https://elixir-slack.community/"
171
+
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-base-200 hover:text-base-content"
172
+
>
173
+
<svg
174
+
viewBox="0 0 16 16"
175
+
aria-hidden="true"
176
+
class="h-4 w-4 fill-base-content/40 group-hover:fill-base-content"
177
+
>
178
+
<path d="M3.361 10.11a1.68 1.68 0 1 1-1.68-1.681h1.68v1.682ZM4.209 10.11a1.68 1.68 0 1 1 3.361 0v4.21a1.68 1.68 0 1 1-3.361 0v-4.21ZM5.89 3.361a1.68 1.68 0 1 1 1.681-1.68v1.68H5.89ZM5.89 4.209a1.68 1.68 0 1 1 0 3.361H1.68a1.68 1.68 0 1 1 0-3.361h4.21ZM12.639 5.89a1.68 1.68 0 1 1 1.68 1.681h-1.68V5.89ZM11.791 5.89a1.68 1.68 0 1 1-3.361 0V1.68a1.68 1.68 0 0 1 3.361 0v4.21ZM10.11 12.639a1.68 1.68 0 1 1-1.681 1.68v-1.68h1.682ZM10.11 11.791a1.68 1.68 0 1 1 0-3.361h4.21a1.68 1.68 0 1 1 0 3.361h-4.21Z" />
179
+
</svg>
180
+
Join us on Slack
181
+
</a>
182
+
</div>
183
+
<div>
184
+
<a
185
+
href="https://fly.io/docs/elixir/getting-started/"
186
+
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-base-200 hover:text-base-content"
187
+
>
188
+
<svg
189
+
viewBox="0 0 20 20"
190
+
aria-hidden="true"
191
+
class="h-4 w-4 fill-base-content/40 group-hover:fill-base-content"
192
+
>
193
+
<path d="M1 12.5A4.5 4.5 0 005.5 17H15a4 4 0 001.866-7.539 3.504 3.504 0 00-4.504-4.272A4.5 4.5 0 004.06 8.235 4.502 4.502 0 001 12.5z" />
194
+
</svg>
195
+
Deploy your application
196
+
</a>
197
+
</div>
198
+
</div>
199
+
</div>
200
+
</div>
201
+
</div>
202
+
</div>
+10
lib/comet_web/controllers/page_html.ex
+10
lib/comet_web/controllers/page_html.ex
+56
lib/comet_web/endpoint.ex
+56
lib/comet_web/endpoint.ex
···
1
+
defmodule CometWeb.Endpoint do
2
+
use Phoenix.Endpoint, otp_app: :comet
3
+
4
+
# The session will be stored in the cookie and signed,
5
+
# this means its contents can be read but not tampered with.
6
+
# Set :encryption_salt if you would also like to encrypt it.
7
+
@session_options [
8
+
store: :cookie,
9
+
key: "_comet_key",
10
+
signing_salt: "a/yCy9X7",
11
+
same_site: "Lax"
12
+
]
13
+
14
+
socket "/live", Phoenix.LiveView.Socket,
15
+
websocket: [connect_info: [session: @session_options]],
16
+
longpoll: [connect_info: [session: @session_options]]
17
+
18
+
# Serve at "/" the static files from "priv/static" directory.
19
+
#
20
+
# When code reloading is disabled (e.g., in production),
21
+
# the `gzip` option is enabled to serve compressed
22
+
# static files generated by running `phx.digest`.
23
+
plug Plug.Static,
24
+
at: "/",
25
+
from: :comet,
26
+
gzip: not code_reloading?,
27
+
only: ["hologram" | CometWeb.static_paths()],
28
+
raise_on_missing_only: code_reloading?
29
+
30
+
# Code reloading can be explicitly enabled under the
31
+
# :code_reloader configuration of your endpoint.
32
+
if code_reloading? do
33
+
socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
34
+
plug Phoenix.LiveReloader
35
+
plug Phoenix.CodeReloader
36
+
plug Phoenix.Ecto.CheckRepoStatus, otp_app: :comet
37
+
end
38
+
39
+
plug Phoenix.LiveDashboard.RequestLogger,
40
+
param_key: "request_logger",
41
+
cookie_key: "request_logger"
42
+
43
+
plug Plug.RequestId
44
+
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
45
+
46
+
plug Plug.Parsers,
47
+
parsers: [:urlencoded, :multipart, :json],
48
+
pass: ["*/*"],
49
+
json_decoder: Phoenix.json_library()
50
+
51
+
plug Plug.MethodOverride
52
+
plug Plug.Head
53
+
plug Plug.Session, @session_options
54
+
plug Hologram.Router
55
+
plug CometWeb.Router
56
+
end
+25
lib/comet_web/gettext.ex
+25
lib/comet_web/gettext.ex
···
1
+
defmodule CometWeb.Gettext do
2
+
@moduledoc """
3
+
A module providing Internationalization with a gettext-based API.
4
+
5
+
By using [Gettext](https://hexdocs.pm/gettext), your module compiles translations
6
+
that you can use in your application. To use this Gettext backend module,
7
+
call `use Gettext` and pass it as an option:
8
+
9
+
use Gettext, backend: CometWeb.Gettext
10
+
11
+
# Simple translation
12
+
gettext("Here is the string to translate")
13
+
14
+
# Plural translation
15
+
ngettext("Here is the string to translate",
16
+
"Here are the strings to translate",
17
+
3)
18
+
19
+
# Domain-based translation
20
+
dgettext("errors", "Here is the error message to translate")
21
+
22
+
See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
23
+
"""
24
+
use Gettext.Backend, otp_app: :comet
25
+
end
+44
lib/comet_web/router.ex
+44
lib/comet_web/router.ex
···
1
+
defmodule CometWeb.Router do
2
+
use CometWeb, :router
3
+
4
+
pipeline :browser do
5
+
plug :accepts, ["html"]
6
+
plug :fetch_session
7
+
plug :fetch_live_flash
8
+
plug :put_root_layout, html: {CometWeb.Layouts, :root}
9
+
plug :protect_from_forgery
10
+
plug :put_secure_browser_headers
11
+
end
12
+
13
+
pipeline :api do
14
+
plug :accepts, ["json"]
15
+
end
16
+
17
+
scope "/", CometWeb do
18
+
pipe_through :browser
19
+
20
+
get "/", PageController, :home
21
+
end
22
+
23
+
# Other scopes may use custom stacks.
24
+
# scope "/api", CometWeb do
25
+
# pipe_through :api
26
+
# end
27
+
28
+
# Enable LiveDashboard and Swoosh mailbox preview in development
29
+
if Application.compile_env(:comet, :dev_routes) do
30
+
# If you want to use the LiveDashboard in production, you should put
31
+
# it behind authentication and allow only admins to access it.
32
+
# If your application does not have an admins-only section yet,
33
+
# you can use Plug.BasicAuth to set up some basic authentication
34
+
# as long as you are also using SSL (which you should anyway).
35
+
import Phoenix.LiveDashboard.Router
36
+
37
+
scope "/dev" do
38
+
pipe_through :browser
39
+
40
+
live_dashboard "/dashboard", metrics: CometWeb.Telemetry
41
+
forward "/mailbox", Plug.Swoosh.MailboxPreview
42
+
end
43
+
end
44
+
end
+93
lib/comet_web/telemetry.ex
+93
lib/comet_web/telemetry.ex
···
1
+
defmodule CometWeb.Telemetry do
2
+
use Supervisor
3
+
import Telemetry.Metrics
4
+
5
+
def start_link(arg) do
6
+
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
7
+
end
8
+
9
+
@impl true
10
+
def init(_arg) do
11
+
children = [
12
+
# Telemetry poller will execute the given period measurements
13
+
# every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
14
+
{:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
15
+
# Add reporters as children of your supervision tree.
16
+
# {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
17
+
]
18
+
19
+
Supervisor.init(children, strategy: :one_for_one)
20
+
end
21
+
22
+
def metrics do
23
+
[
24
+
# Phoenix Metrics
25
+
summary("phoenix.endpoint.start.system_time",
26
+
unit: {:native, :millisecond}
27
+
),
28
+
summary("phoenix.endpoint.stop.duration",
29
+
unit: {:native, :millisecond}
30
+
),
31
+
summary("phoenix.router_dispatch.start.system_time",
32
+
tags: [:route],
33
+
unit: {:native, :millisecond}
34
+
),
35
+
summary("phoenix.router_dispatch.exception.duration",
36
+
tags: [:route],
37
+
unit: {:native, :millisecond}
38
+
),
39
+
summary("phoenix.router_dispatch.stop.duration",
40
+
tags: [:route],
41
+
unit: {:native, :millisecond}
42
+
),
43
+
summary("phoenix.socket_connected.duration",
44
+
unit: {:native, :millisecond}
45
+
),
46
+
sum("phoenix.socket_drain.count"),
47
+
summary("phoenix.channel_joined.duration",
48
+
unit: {:native, :millisecond}
49
+
),
50
+
summary("phoenix.channel_handled_in.duration",
51
+
tags: [:event],
52
+
unit: {:native, :millisecond}
53
+
),
54
+
55
+
# Database Metrics
56
+
summary("comet.repo.query.total_time",
57
+
unit: {:native, :millisecond},
58
+
description: "The sum of the other measurements"
59
+
),
60
+
summary("comet.repo.query.decode_time",
61
+
unit: {:native, :millisecond},
62
+
description: "The time spent decoding the data received from the database"
63
+
),
64
+
summary("comet.repo.query.query_time",
65
+
unit: {:native, :millisecond},
66
+
description: "The time spent executing the query"
67
+
),
68
+
summary("comet.repo.query.queue_time",
69
+
unit: {:native, :millisecond},
70
+
description: "The time spent waiting for a database connection"
71
+
),
72
+
summary("comet.repo.query.idle_time",
73
+
unit: {:native, :millisecond},
74
+
description:
75
+
"The time the connection spent waiting before being checked out for the query"
76
+
),
77
+
78
+
# VM Metrics
79
+
summary("vm.memory.total", unit: {:byte, :kilobyte}),
80
+
summary("vm.total_run_queue_lengths.total"),
81
+
summary("vm.total_run_queue_lengths.cpu"),
82
+
summary("vm.total_run_queue_lengths.io")
83
+
]
84
+
end
85
+
86
+
defp periodic_measurements do
87
+
[
88
+
# A module, function and arguments to be invoked periodically.
89
+
# This function must call :telemetry.execute/3 and a metric must be added above.
90
+
# {CometWeb, :count_users, []}
91
+
]
92
+
end
93
+
end
+114
lib/comet_web.ex
+114
lib/comet_web.ex
···
1
+
defmodule CometWeb do
2
+
@moduledoc """
3
+
The entrypoint for defining your web interface, such
4
+
as controllers, components, channels, and so on.
5
+
6
+
This can be used in your application as:
7
+
8
+
use CometWeb, :controller
9
+
use CometWeb, :html
10
+
11
+
The definitions below will be executed for every controller,
12
+
component, etc, so keep them short and clean, focused
13
+
on imports, uses and aliases.
14
+
15
+
Do NOT define functions inside the quoted expressions
16
+
below. Instead, define additional modules and import
17
+
those modules here.
18
+
"""
19
+
20
+
def static_paths, do: ~w(assets fonts images favicon.ico robots.txt)
21
+
22
+
def router do
23
+
quote do
24
+
use Phoenix.Router, helpers: false
25
+
26
+
# Import common connection and controller functions to use in pipelines
27
+
import Plug.Conn
28
+
import Phoenix.Controller
29
+
import Phoenix.LiveView.Router
30
+
end
31
+
end
32
+
33
+
def channel do
34
+
quote do
35
+
use Phoenix.Channel
36
+
end
37
+
end
38
+
39
+
def controller do
40
+
quote do
41
+
use Phoenix.Controller, formats: [:html, :json]
42
+
43
+
use Gettext, backend: CometWeb.Gettext
44
+
45
+
import Plug.Conn
46
+
47
+
unquote(verified_routes())
48
+
end
49
+
end
50
+
51
+
def live_view do
52
+
quote do
53
+
use Phoenix.LiveView
54
+
55
+
unquote(html_helpers())
56
+
end
57
+
end
58
+
59
+
def live_component do
60
+
quote do
61
+
use Phoenix.LiveComponent
62
+
63
+
unquote(html_helpers())
64
+
end
65
+
end
66
+
67
+
def html do
68
+
quote do
69
+
use Phoenix.Component
70
+
71
+
# Import convenience functions from controllers
72
+
import Phoenix.Controller,
73
+
only: [get_csrf_token: 0, view_module: 1, view_template: 1]
74
+
75
+
# Include general helpers for rendering HTML
76
+
unquote(html_helpers())
77
+
end
78
+
end
79
+
80
+
defp html_helpers do
81
+
quote do
82
+
# Translation
83
+
use Gettext, backend: CometWeb.Gettext
84
+
85
+
# HTML escaping functionality
86
+
import Phoenix.HTML
87
+
# Core UI components
88
+
import CometWeb.CoreComponents
89
+
90
+
# Common modules used in templates
91
+
alias Phoenix.LiveView.JS
92
+
alias CometWeb.Layouts
93
+
94
+
# Routes generation with the ~p sigil
95
+
unquote(verified_routes())
96
+
end
97
+
end
98
+
99
+
def verified_routes do
100
+
quote do
101
+
use Phoenix.VerifiedRoutes,
102
+
endpoint: CometWeb.Endpoint,
103
+
router: CometWeb.Router,
104
+
statics: CometWeb.static_paths()
105
+
end
106
+
end
107
+
108
+
@doc """
109
+
When used, dispatch to the appropriate controller/live_view/etc.
110
+
"""
111
+
defmacro __using__(which) when is_atom(which) do
112
+
apply(__MODULE__, which, [])
113
+
end
114
+
end
+103
mix.exs
+103
mix.exs
···
1
+
defmodule Comet.MixProject do
2
+
use Mix.Project
3
+
4
+
def project do
5
+
[
6
+
app: :comet,
7
+
version: "0.1.0",
8
+
elixir: "~> 1.15",
9
+
elixirc_paths: elixirc_paths(Mix.env()),
10
+
start_permanent: Mix.env() == :prod,
11
+
aliases: aliases(),
12
+
deps: deps(),
13
+
compilers: [:phoenix_live_view, :hologram] ++ Mix.compilers(),
14
+
listeners: [Phoenix.CodeReloader]
15
+
]
16
+
end
17
+
18
+
# Configuration for the OTP application.
19
+
#
20
+
# Type `mix help compile.app` for more information.
21
+
def application do
22
+
[
23
+
mod: {Comet.Application, []},
24
+
extra_applications: [:logger, :runtime_tools]
25
+
]
26
+
end
27
+
28
+
def cli do
29
+
[
30
+
preferred_envs: [precommit: :test]
31
+
]
32
+
end
33
+
34
+
# Specifies which paths to compile per environment.
35
+
defp elixirc_paths(:test), do: ["lib", "test/support"]
36
+
defp elixirc_paths(_), do: ["lib"]
37
+
38
+
# Specifies your project dependencies.
39
+
#
40
+
# Type `mix help deps` for examples and options.
41
+
defp deps do
42
+
[
43
+
{:phoenix, "~> 1.8.2"},
44
+
{:phoenix_ecto, "~> 4.5"},
45
+
{:ecto_sql, "~> 3.13"},
46
+
{:postgrex, ">= 0.0.0"},
47
+
{:phoenix_html, "~> 4.1"},
48
+
{:phoenix_live_reload, "~> 1.2", only: :dev},
49
+
{:phoenix_live_view, "~> 1.1.0"},
50
+
{:lazy_html, ">= 0.1.0", only: :test},
51
+
{:phoenix_live_dashboard, "~> 0.8.3"},
52
+
{:esbuild, "~> 0.10", runtime: Mix.env() == :dev},
53
+
{:tailwind, "~> 0.3", runtime: Mix.env() == :dev},
54
+
{:heroicons,
55
+
github: "tailwindlabs/heroicons",
56
+
tag: "v2.2.0",
57
+
sparse: "optimized",
58
+
app: false,
59
+
compile: false,
60
+
depth: 1},
61
+
{:swoosh, "~> 1.16"},
62
+
{:req, "~> 0.5"},
63
+
{:telemetry_metrics, "~> 1.0"},
64
+
{:telemetry_poller, "~> 1.0"},
65
+
{:gettext, "~> 1.0"},
66
+
{:jason, "~> 1.2"},
67
+
{:dns_cluster, "~> 0.2.0"},
68
+
{:bandit, "~> 1.5"},
69
+
{:atex, "~> 0.6"},
70
+
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
71
+
{:drinkup, "~> 0.1"},
72
+
{:typedstruct, "~> 0.5"},
73
+
{:hologram, "~> 0.6.5"}
74
+
]
75
+
end
76
+
77
+
# Aliases are shortcuts or tasks specific to the current project.
78
+
# For example, to install project dependencies and perform other setup tasks, run:
79
+
#
80
+
# $ mix setup
81
+
#
82
+
# See the documentation for `Mix` for more info on aliases.
83
+
defp aliases do
84
+
[
85
+
setup: ["deps.get", "ecto.setup", "assets.setup", "assets.build"],
86
+
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
87
+
"ecto.reset": ["ecto.drop", "ecto.setup"],
88
+
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
89
+
"assets.setup": [
90
+
"cmd pnpm install",
91
+
# "tailwind.install --if-missing",
92
+
"esbuild.install --if-missing"
93
+
],
94
+
"assets.build": ["compile", "tailwind comet", "esbuild comet"],
95
+
"assets.deploy": [
96
+
"tailwind comet --minify",
97
+
"esbuild comet --minify",
98
+
"phx.digest"
99
+
],
100
+
precommit: ["compile --warnings-as-errors", "deps.unlock --unused", "format", "test"]
101
+
]
102
+
end
103
+
end
+69
mix.lock
+69
mix.lock
···
1
+
%{
2
+
"atex": {:hex, :atex, "0.6.0", "a02f3c1b3ef04d8cd30243a05fc3629929c66d826e1d6a7e9d4ec6076ac89aea", [:mix], [{:ex_cldr, "~> 2.42", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:jose, "~> 1.11", [hex: :jose, repo: "hexpm", optional: false]}, {:multiformats_ex, "~> 0.2", [hex: :multiformats_ex, repo: "hexpm", optional: false]}, {:peri, "~> 0.6", [hex: :peri, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:recase, "~> 0.5", [hex: :recase, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:typedstruct, "~> 0.5", [hex: :typedstruct, repo: "hexpm", optional: false]}], "hexpm", "a3615e361e1e1b2887910834b3ede88680a02e4d585243ea90d0a6394f688aa2"},
3
+
"bandit": {:hex, :bandit, "1.8.0", "c2e93d7e3c5c794272fa4623124f827c6f24b643acc822be64c826f9447d92fb", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "8458ff4eed20ff2a2ea69d4854883a077c33ea42b51f6811b044ceee0fa15422"},
4
+
"beam_file": {:hex, :beam_file, "0.6.2", "efd54ec60be6a03f0a8f96f72b0353427196613289c46032d3500f0ab6c34d32", [:mix], [], "hexpm", "09a99e8e5aad674edcad7213b0d7602375dfd3c7d02f8e3136e3efae0bcc9c56"},
5
+
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
6
+
"car": {:hex, :car, "0.1.1", "a5bc4c5c1be96eab437634b3c0ccad1fe17b5e3d68c22a4031241ae1345aebd4", [:mix], [{:cbor, "~> 1.0.0", [hex: :cbor, repo: "hexpm", optional: false]}, {:typedstruct, "~> 0.5", [hex: :typedstruct, repo: "hexpm", optional: false]}, {:varint, "~> 1.4", [hex: :varint, repo: "hexpm", optional: false]}], "hexpm", "f895dda8123d04dd336db5a2bf0d0b47f4559cd5383f83fcca0700c1b45bfb6a"},
7
+
"cbor": {:hex, :cbor, "1.0.1", "39511158e8ea5a57c1fcb9639aaa7efde67129678fee49ebbda780f6f24959b0", [:mix], [], "hexpm", "5431acbe7a7908f17f6a9cd43311002836a34a8ab01876918d8cfb709cd8b6a2"},
8
+
"cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"},
9
+
"certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"},
10
+
"cldr_utils": {:hex, :cldr_utils, "2.29.1", "11ff0a50a36a7e5f3bd9fc2fb8486a4c1bcca3081d9c080bf9e48fe0e6742e2d", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "3844a0a0ed7f42e6590ddd8bd37eb4b1556b112898f67dea3ba068c29aabd6c2"},
11
+
"cowlib": {:hex, :cowlib, "2.16.0", "54592074ebbbb92ee4746c8a8846e5605052f29309d3a873468d76cdf932076f", [:make, :rebar3], [], "hexpm", "7f478d80d66b747344f0ea7708c187645cfcc08b11aa424632f78e25bf05db51"},
12
+
"credo": {:hex, :credo, "1.7.13", "126a0697df6b7b71cd18c81bc92335297839a806b6f62b61d417500d1070ff4e", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "47641e6d2bbff1e241e87695b29f617f1a8f912adea34296fb10ecc3d7e9e84f"},
13
+
"db_connection": {:hex, :db_connection, "2.8.1", "9abdc1e68c34c6163f6fb96a96532272d13ad7ca45262156ae8b7ec6d9dc4bec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61a3d489b239d76f326e03b98794fb8e45168396c925ef25feb405ed09da8fd"},
14
+
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
15
+
"dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"},
16
+
"drinkup": {:hex, :drinkup, "0.1.0", "a21d563163a0a19db448820f0c4cf9278d52e94b7276b099c60cb8544b16b14e", [:mix], [{:car, "~> 0.1.0", [hex: :car, repo: "hexpm", optional: false]}, {:cbor, "~> 1.0.0", [hex: :cbor, repo: "hexpm", optional: false]}, {:certifi, "~> 2.15", [hex: :certifi, repo: "hexpm", optional: false]}, {:gun, "~> 2.2", [hex: :gun, repo: "hexpm", optional: false]}, {:typedstruct, "~> 0.5", [hex: :typedstruct, repo: "hexpm", optional: false]}], "hexpm", "e2a22b12386936f4a62c6307050c3484c41ecd07c50dc1b1af653195d539baa2"},
17
+
"ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"},
18
+
"ecto_sql": {:hex, :ecto_sql, "3.13.2", "a07d2461d84107b3d037097c822ffdd36ed69d1cf7c0f70e12a3d1decf04e2e1", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "539274ab0ecf1a0078a6a72ef3465629e4d6018a3028095dc90f60a19c371717"},
19
+
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
20
+
"esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"},
21
+
"ex_cldr": {:hex, :ex_cldr, "2.44.1", "0d220b175874e1ce77a0f7213bdfe700b9be11aefbf35933a0e98837803ebdc5", [:mix], [{:cldr_utils, "~> 2.28", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19 or ~> 1.0", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "3880cd6137ea21c74250cd870d3330c4a9fdec07fabd5e37d1b239547929e29b"},
22
+
"expo": {:hex, :expo, "1.1.1", "4202e1d2ca6e2b3b63e02f69cfe0a404f77702b041d02b58597c00992b601db5", [:mix], [], "hexpm", "5fb308b9cb359ae200b7e23d37c76978673aa1b06e2b3075d814ce12c5811640"},
23
+
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
24
+
"finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"},
25
+
"fine": {:hex, :fine, "0.1.4", "b19a89c1476c7c57afb5f9314aed5960b5bc95d5277de4cb5ee8e1d1616ce379", [:mix], [], "hexpm", "be3324cc454a42d80951cf6023b9954e9ff27c6daa255483b3e8d608670303f5"},
26
+
"gettext": {:hex, :gettext, "1.0.2", "5457e1fd3f4abe47b0e13ff85086aabae760497a3497909b8473e0acee57673b", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "eab805501886802071ad290714515c8c4a17196ea76e5afc9d06ca85fb1bfeb3"},
27
+
"gproc": {:hex, :gproc, "1.0.0", "aa9ec57f6c9ff065b16d96924168d7c7157cd1fd457680efe4b1274f456fa500", [:rebar3], [], "hexpm", "109f253c2787de8a371a51179d4973230cbec6239ee673fa12216a5ce7e4f902"},
28
+
"gun": {:hex, :gun, "2.2.0", "b8f6b7d417e277d4c2b0dc3c07dfdf892447b087f1cc1caff9c0f556b884e33d", [:make, :rebar3], [{:cowlib, ">= 2.15.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "76022700c64287feb4df93a1795cff6741b83fb37415c40c34c38d2a4645261a"},
29
+
"heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "0435d4ca364a608cc75e2f8683d374e55abbae26", [tag: "v2.2.0", sparse: "optimized", depth: 1]},
30
+
"hologram": {:hex, :hologram, "0.6.5", "355312e414489f590520efa2ccccf1719c415793225a87330407772efcaa0a78", [:mix], [{:beam_file, "0.6.2", [hex: :beam_file, repo: "hexpm", optional: false]}, {:file_system, "~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:gproc, "~> 1.0", [hex: :gproc, repo: "hexpm", optional: false]}, {:html_entities, "~> 0.5", [hex: :html_entities, repo: "hexpm", optional: false]}, {:interceptor, "~> 0.5", [hex: :interceptor, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:uuid, "~> 1.0", [hex: :uuid, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8b351690087b9980e60d4a2992cfceb4637e2a2082afdfc78060f569299abdc6"},
31
+
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
32
+
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
33
+
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
34
+
"interceptor": {:hex, :interceptor, "0.5.4", "158e019413439714306d52ff5189533b1236ab776b6f7a1ce63ace079f84867c", [:mix], [], "hexpm", "a4fa78a73199832222c4bb47c57e5b7384f7b49d43c09a57c6e731086f219f95"},
35
+
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
36
+
"jose": {:hex, :jose, "1.11.12", "06e62b467b61d3726cbc19e9b5489f7549c37993de846dfb3ee8259f9ed208b3", [:mix, :rebar3], [], "hexpm", "31e92b653e9210b696765cdd885437457de1add2a9011d92f8cf63e4641bab7b"},
37
+
"lazy_html": {:hex, :lazy_html, "0.1.8", "677a8642e644eef8de98f3040e2520d42d0f0f8bd6c5cd49db36504e34dffe91", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.9.0", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1.0", [hex: :fine, repo: "hexpm", optional: false]}], "hexpm", "0d8167d930b704feb94b41414ca7f5779dff9bca7fcf619fcef18de138f08736"},
38
+
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
39
+
"mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"},
40
+
"multiformats_ex": {:hex, :multiformats_ex, "0.2.0", "5b0a3faa1a770dc671aa8a89b6323cc20b0ecf67dc93dcd21312151fbea6b4ee", [:mix], [{:varint, "~> 1.4", [hex: :varint, repo: "hexpm", optional: false]}], "hexpm", "aa406d9addb06dc197e0e92212992486af6599158d357680f29f2d11e08d0423"},
41
+
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
42
+
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
43
+
"peri": {:hex, :peri, "0.6.2", "3c043bfb6aa18eb1ea41d80981d19294c5e943937b1311e8e958da3581139061", [:mix], [{:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.1", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "5e0d8e0bd9de93d0f8e3ad6b9a5bd143f7349c025196ef4a3591af93ce6ecad9"},
44
+
"phoenix": {:hex, :phoenix, "1.8.2", "75aba5b90081d88a54f2fc6a26453d4e76762ab095ff89be5a3e7cb28bff9300", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "19ea65b4064f17b1ab0515595e4d0ea65742ab068259608d5d7b139a73f47611"},
45
+
"phoenix_ecto": {:hex, :phoenix_ecto, "4.7.0", "75c4b9dfb3efdc42aec2bd5f8bccd978aca0651dbcbc7a3f362ea5d9d43153c6", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "1d75011e4254cb4ddf823e81823a9629559a1be93b4321a6a5f11a5306fbf4cc"},
46
+
"phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"},
47
+
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"},
48
+
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.6.1", "05df733a09887a005ed0d69a7fc619d376aea2730bf64ce52ac51ce716cc1ef0", [:mix], [{:file_system, "~> 0.2.10 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "74273843d5a6e4fef0bbc17599f33e3ec63f08e69215623a0cd91eea4288e5a0"},
49
+
"phoenix_live_view": {:hex, :phoenix_live_view, "1.1.18", "b5410017b3d4edf261d9c98ebc334e0637d7189457c730720cfc13e206443d43", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f189b759595feff0420e9a1d544396397f9cf9e2d5a8cb98ba5b6cab01927da0"},
50
+
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"},
51
+
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
52
+
"plug": {:hex, :plug, "1.18.1", "5067f26f7745b7e31bc3368bc1a2b818b9779faa959b49c934c17730efc911cf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "57a57db70df2b422b564437d2d33cf8d33cd16339c1edb190cd11b1a3a546cc2"},
53
+
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
54
+
"postgrex": {:hex, :postgrex, "0.21.1", "2c5cc830ec11e7a0067dd4d623c049b3ef807e9507a424985b8dcf921224cd88", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "27d8d21c103c3cc68851b533ff99eef353e6a0ff98dc444ea751de43eb48bdac"},
55
+
"recase": {:hex, :recase, "0.9.1", "82d2e2e2d4f9e92da1ce5db338ede2e4f15a50ac1141fc082b80050b9f49d96e", [:mix], [], "hexpm", "19ba03ceb811750e6bec4a015a9f9e45d16a8b9e09187f6d72c3798f454710f3"},
56
+
"req": {:hex, :req, "0.5.16", "99ba6a36b014458e52a8b9a0543bfa752cb0344b2a9d756651db1281d4ba4450", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "974a7a27982b9b791df84e8f6687d21483795882a7840e8309abdbe08bb06f09"},
57
+
"swoosh": {:hex, :swoosh, "1.19.8", "0576f2ea96d1bb3a6e02cc9f79cbd7d497babc49a353eef8dce1a1f9f82d7915", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:idna, "~> 6.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d7503c2daf0f9899afd8eba9923eeddef4b62e70816e1d3b6766e4d6c60e94ad"},
58
+
"tailwind": {:hex, :tailwind, "0.4.1", "e7bcc222fe96a1e55f948e76d13dd84a1a7653fb051d2a167135db3b4b08d3e9", [:mix], [], "hexpm", "6249d4f9819052911120dbdbe9e532e6bd64ea23476056adb7f730aa25c220d1"},
59
+
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
60
+
"telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"},
61
+
"telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"},
62
+
"thousand_island": {:hex, :thousand_island, "1.4.2", "735fa783005d1703359bbd2d3a5a3a398075ba4456e5afe3c5b7cf4666303d36", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1c7637f16558fc1c35746d5ee0e83b18b8e59e18d28affd1f2fa1645f8bc7473"},
63
+
"typedstruct": {:hex, :typedstruct, "0.5.4", "d1d33d58460a74f413e9c26d55e66fd633abd8ac0fb12639add9a11a60a0462a", [:make, :mix], [], "hexpm", "ffaef36d5dbaebdbf4ed07f7fb2ebd1037b2c1f757db6fb8e7bcbbfabbe608d8"},
64
+
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"},
65
+
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"},
66
+
"varint": {:hex, :varint, "1.5.1", "17160c70d0428c3f8a7585e182468cac10bbf165c2360cf2328aaa39d3fb1795", [:mix], [], "hexpm", "24f3deb61e91cb988056de79d06f01161dd01be5e0acae61d8d936a552f1be73"},
67
+
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
68
+
"websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"},
69
+
}
+12
-15
package.json
+12
-15
package.json
···
1
1
{
2
2
"name": "@comet/workspace",
3
3
"version": "1.0.0",
4
+
"devDependencies": {
5
+
"@tailwindcss/cli": "^4.1.17",
6
+
"prettier": "^3.5.3",
7
+
"prettier-plugin-tailwindcss": "^0.6.11",
8
+
"tailwindcss": "^4.1.17",
9
+
"typescript": "^5"
10
+
},
4
11
"private": true,
5
-
"type": "module",
6
-
"workspaces": [
7
-
"apps/*",
8
-
"packages/*"
9
-
],
10
12
"scripts": {
11
-
"format": "prettier --write .",
12
-
"lint": "prettier --check . && eslint ."
13
+
"format": "prettier --write ."
13
14
},
14
-
"devDependencies": {
15
-
"@types/bun": "latest",
16
-
"prettier": "^3.5.3",
17
-
"prettier-plugin-svelte": "^3.4.0",
18
-
"prettier-plugin-tailwindcss": "^0.6.11"
19
-
},
20
-
"peerDependencies": {
21
-
"typescript": "^5"
15
+
"type": "commonjs",
16
+
"workspaces": [],
17
+
"dependencies": {
18
+
"@fontsource-variable/work-sans": "^5.2.8"
22
19
}
23
20
}
-34
packages/lexicons/.gitignore
-34
packages/lexicons/.gitignore
···
1
-
# dependencies (bun install)
2
-
node_modules
3
-
4
-
# output
5
-
out
6
-
dist
7
-
*.tgz
8
-
9
-
# code coverage
10
-
coverage
11
-
*.lcov
12
-
13
-
# logs
14
-
logs
15
-
_.log
16
-
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
17
-
18
-
# dotenv environment variable files
19
-
.env
20
-
.env.development.local
21
-
.env.test.local
22
-
.env.production.local
23
-
.env.local
24
-
25
-
# caches
26
-
.eslintcache
27
-
.cache
28
-
*.tsbuildinfo
29
-
30
-
# IntelliJ based IDEs
31
-
.idea
32
-
33
-
# Finder (MacOS) folder config
34
-
.DS_Store
packages/lexicons/README.md
packages/lexicons/README.md
This is a binary file and will not be displayed.
-16
packages/lexicons/package.json
-16
packages/lexicons/package.json
···
1
-
{
2
-
"name": "lexicons",
3
-
"module": "index.ts",
4
-
"type": "module",
5
-
"private": true,
6
-
"devDependencies": {
7
-
"@atproto/lex-cli": "^0.8.1",
8
-
"@types/bun": "latest"
9
-
},
10
-
"peerDependencies": {
11
-
"typescript": "^5"
12
-
},
13
-
"dependencies": {
14
-
"@atproto/lexicon": "^0.4.11"
15
-
}
16
-
}
-51
packages/lexicons/src/sh/comet/v0/actor/profile.json
-51
packages/lexicons/src/sh/comet/v0/actor/profile.json
···
1
-
{
2
-
"lexicon": 1,
3
-
"id": "sh.comet.v0.actor.profile",
4
-
"defs": {
5
-
"main": {
6
-
"type": "record",
7
-
"description": "A user's Comet profile.",
8
-
"key": "literal:self",
9
-
"record": {
10
-
"type": "object",
11
-
"properties": {
12
-
"displayName": {
13
-
"type": "string",
14
-
"maxGraphemes": 64,
15
-
"maxLength": 640
16
-
},
17
-
"description": {
18
-
"type": "string",
19
-
"description": "Free-form profile description text.",
20
-
"maxGraphemes": 256,
21
-
"maxLength": 2560
22
-
},
23
-
"descriptionFacets": {
24
-
"type": "ref",
25
-
"description": "Annotations of the user's description.",
26
-
"ref": "sh.comet.v0.richtext.facets"
27
-
},
28
-
"avatar": {
29
-
"type": "blob",
30
-
"description": "Small image to be displayed next to posts from account. AKA, 'profile picture'",
31
-
"accept": ["image/png", "image/jpeg"],
32
-
"maxSize": 1000000
33
-
},
34
-
"featured": {
35
-
"type": "array",
36
-
"items": {
37
-
"type": "ref",
38
-
"ref": "com.atproto.repo.strongRef"
39
-
}
40
-
},
41
-
"createdAt": { "type": "string", "format": "datetime" }
42
-
}
43
-
}
44
-
},
45
-
"view": {},
46
-
"viewBasic": {
47
-
"type": "object",
48
-
"properties": {}
49
-
}
50
-
}
51
-
}
-1
packages/lexicons/src/sh/comet/v0/feed/comment.json
-1
packages/lexicons/src/sh/comet/v0/feed/comment.json
···
1
-
{}
-54
packages/lexicons/src/sh/comet/v0/feed/defs.json
-54
packages/lexicons/src/sh/comet/v0/feed/defs.json
···
1
-
{
2
-
"lexicon": 1,
3
-
"id": "sh.comet.v0.feed.defs",
4
-
"defs": {
5
-
"tag": {
6
-
"type": "string",
7
-
"knownValues": [
8
-
"sh.comet.v0.feed.tag#alternative",
9
-
"sh.comet.v0.feed.tag#ambient",
10
-
"sh.comet.v0.feed.tag#electronic",
11
-
"sh.comet.v0.feed.tag#experimental",
12
-
"sh.comet.v0.feed.tag#hiphop",
13
-
"sh.comet.v0.feed.tag#metal",
14
-
"sh.comet.v0.feed.tag#rock",
15
-
"sh.comet.v0.feed.tag#podcast",
16
-
"sh.comet.v0.feed.tag#pop",
17
-
"sh.comet.v0.feed.tag#punk"
18
-
]
19
-
},
20
-
"link": {
21
-
"type": "object",
22
-
"description": "Link for the track. Usually to acquire it in some way, e.g. via free download or purchase. | TODO: multiple links?",
23
-
"required": ["type", "value"],
24
-
"properties": {
25
-
"type": {
26
-
"type": "string",
27
-
"knownValues": [
28
-
"sh.comet.v0.feed.defs#downloadLink",
29
-
"sh.comet.v0.feed.defs#buyLink"
30
-
]
31
-
},
32
-
"value": { "type": "string", "format": "uri" }
33
-
}
34
-
},
35
-
"downloadLink": {
36
-
"type": "token",
37
-
"description": "Indicate the link leads to a free download for the track."
38
-
},
39
-
"buyLink": {
40
-
"type": "token",
41
-
"description": "Indicate the link leads to a purchase page for the track."
42
-
},
43
-
"viewerState": {
44
-
"type": "object",
45
-
"description": "Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests.",
46
-
"properties": {
47
-
"like": { "type": "string", "format": "at-uri" },
48
-
"repost": { "type": "string", "format": "at-uri" }
49
-
// TODO: pinned items
50
-
// TODO: maybe what personal playlists it's in?
51
-
}
52
-
}
53
-
}
54
-
}
-19
packages/lexicons/src/sh/comet/v0/feed/like.json
-19
packages/lexicons/src/sh/comet/v0/feed/like.json
···
1
-
{
2
-
"lexicon": 1,
3
-
"id": "sh.comet.v0.feed.like",
4
-
"defs": {
5
-
"main": {
6
-
"type": "record",
7
-
"description": "Record representing a 'like' of some media.",
8
-
"key": "tid",
9
-
"record": {
10
-
"type": "object",
11
-
"required": ["subject", "createdAt"],
12
-
"properties": {
13
-
"subject": { "type": "ref", "ref": "com.atproto.repo.strongRef" },
14
-
"createdAt": { "type": "string", "format": "datetime" }
15
-
}
16
-
}
17
-
}
18
-
}
19
-
}
-19
packages/lexicons/src/sh/comet/v0/feed/play.json
-19
packages/lexicons/src/sh/comet/v0/feed/play.json
···
1
-
{
2
-
"lexicon": 1,
3
-
"id": "sh.comet.v0.feed.play",
4
-
"defs": {
5
-
"main": {
6
-
"type": "record",
7
-
"description": "Record representing a 'play' of some media.",
8
-
"key": "tid",
9
-
"record": {
10
-
"type": "object",
11
-
"required": ["subject", "createdAt"],
12
-
"properties": {
13
-
"subject": { "type": "ref", "ref": "com.atproto.repo.strongRef" },
14
-
"createdAt": { "type": "string", "format": "datetime" }
15
-
}
16
-
}
17
-
}
18
-
}
19
-
}
-83
packages/lexicons/src/sh/comet/v0/feed/playlist.json
-83
packages/lexicons/src/sh/comet/v0/feed/playlist.json
···
1
-
{
2
-
"lexicon": 1,
3
-
"id": "sh.comet.v0.feed.playlist",
4
-
"defs": {
5
-
"main": {
6
-
"type": "record",
7
-
"description": "A Comet playlist, containing many audio tracks.",
8
-
"key": "tid",
9
-
"record": {
10
-
"type": "object",
11
-
"required": ["tracks", "title", "type", "createdAt"],
12
-
"properties": {
13
-
"tracks": {
14
-
"type": "array",
15
-
"minLength": 1,
16
-
"items": { "type": "ref", "ref": "com.atproto.repo.strongRef" }
17
-
},
18
-
"image": {
19
-
"type": "blob",
20
-
"description": "Image to be displayed representing the playlist.",
21
-
"accept": ["image/png", "image/jpeg"],
22
-
"maxSize": 1000000
23
-
},
24
-
"title": {
25
-
"type": "string",
26
-
"description": "Title of the playlist.",
27
-
"minLength": 1,
28
-
"maxLength": 2560,
29
-
"maxGraphemes": 256
30
-
},
31
-
"description": {
32
-
"type": "string",
33
-
"description": "Description of the playlist.",
34
-
"maxLength": 20000,
35
-
"maxGraphemes": 2000
36
-
},
37
-
"type": {
38
-
"type": "string",
39
-
"description": "Type of the playlist. Allows differentiating between playlist's different purposes.",
40
-
"knownValues": [
41
-
"sh.comet.v0.feed.playlist#album",
42
-
"sh.comet.v0.feed.playlist#compilation",
43
-
"sh.comet.v0.feed.playlist#playlist",
44
-
"sh.comet.v0.feed.playlist#podcast"
45
-
]
46
-
},
47
-
"tags": {
48
-
"type": "array",
49
-
"description": "Tags/genres for the playlist. First item is used as the \"primary\" tag. | TODO: reconsider maxLength value",
50
-
"minLength": 1,
51
-
"maxLength": 5,
52
-
"items": { "type": "ref", "ref": "sh.comet.v0.feed.defs#tag" }
53
-
},
54
-
"link": {
55
-
"type": "ref",
56
-
"ref": "sh.comet.v0.feed.defs#link"
57
-
},
58
-
"createdAt": {
59
-
"type": "string",
60
-
"format": "datetime",
61
-
"description": "Timestamp for when the playlist was originally created."
62
-
}
63
-
}
64
-
}
65
-
},
66
-
"album": {
67
-
"type": "token",
68
-
"description": "Indicates the playlist is an album or extended play."
69
-
},
70
-
"compilation": {
71
-
"type": "token",
72
-
"description": "Indicates the playlist is a compilation of various tracks, usually put together by a label."
73
-
},
74
-
"playlist": {
75
-
"type": "token",
76
-
"description": "Indicates the playlist is a miscellaneous list of tracks."
77
-
},
78
-
"podcast": {
79
-
"type": "token",
80
-
"description": "Indicates the playlist is a podcast or radio show split into several individual tracks."
81
-
}
82
-
}
83
-
}
-19
packages/lexicons/src/sh/comet/v0/feed/repost.json
-19
packages/lexicons/src/sh/comet/v0/feed/repost.json
···
1
-
{
2
-
"lexicon": 1,
3
-
"id": "sh.comet.v0.feed.repost",
4
-
"defs": {
5
-
"main": {
6
-
"type": "record",
7
-
"description": "Record representing a 'repost' of some media.",
8
-
"key": "tid",
9
-
"record": {
10
-
"type": "object",
11
-
"required": ["subject", "createdAt"],
12
-
"properties": {
13
-
"subject": { "type": "ref", "ref": "com.atproto.repo.strongRef" },
14
-
"createdAt": { "type": "string", "format": "datetime" }
15
-
}
16
-
}
17
-
}
18
-
}
19
-
}
-17
packages/lexicons/src/sh/comet/v0/feed/tag.json
-17
packages/lexicons/src/sh/comet/v0/feed/tag.json
···
1
-
{
2
-
"lexicon": 1,
3
-
"id": "sh.comet.v0.feed.tag",
4
-
"description": "TODO: maybe instead of being like this, make tag a proper record?",
5
-
"defs": {
6
-
"alternative": { "type": "token" },
7
-
"ambient": { "type": "token" },
8
-
"electronic": { "type": "token" },
9
-
"experimental": { "type": "token" },
10
-
"hiphop": { "type": "token" },
11
-
"metal": { "type": "token" },
12
-
"rock": { "type": "token" },
13
-
"podcast": { "type": "token" },
14
-
"pop": { "type": "token" },
15
-
"punk": { "type": "token" }
16
-
}
17
-
}
-91
packages/lexicons/src/sh/comet/v0/feed/track.json
-91
packages/lexicons/src/sh/comet/v0/feed/track.json
···
1
-
{
2
-
"lexicon": 1,
3
-
"id": "sh.comet.v0.feed.track",
4
-
"defs": {
5
-
"main": {
6
-
"type": "record",
7
-
"description": "A Comet audio track.",
8
-
"key": "tid",
9
-
"record": {
10
-
"type": "object",
11
-
"required": ["audio", "title", "createdAt"],
12
-
"properties": {
13
-
"audio": {
14
-
"type": "blob",
15
-
"description": "Audio of the track, ideally encoded as 96k Opus. Limited to 100mb.",
16
-
"accept": ["audio/ogg"],
17
-
"maxSize": 100000000
18
-
},
19
-
"image": {
20
-
"type": "blob",
21
-
"description": "Image to be displayed representing the track.",
22
-
"accept": ["image/png", "image/jpeg"],
23
-
"maxSize": 1000000
24
-
},
25
-
"title": {
26
-
"type": "string",
27
-
"description": "Title of the track. Usually shouldn't include the creator's name.",
28
-
"minLength": 1,
29
-
"maxLength": 2560,
30
-
"maxGraphemes": 256
31
-
},
32
-
"description": {
33
-
"type": "string",
34
-
"description": "Description of the track.",
35
-
"maxLength": 20000,
36
-
"maxGraphemes": 2000
37
-
},
38
-
"descriptionFacets": {
39
-
"type": "ref",
40
-
"description": "Annotations of the track's description.",
41
-
"ref": "sh.comet.v0.richtext.facets"
42
-
},
43
-
"tags": {
44
-
"type": "array",
45
-
"description": "Tags/genres for the track. First item is used as the \"primary\" tag. | TODO: reconsider maxLength value",
46
-
"minLength": 1,
47
-
"maxLength": 5,
48
-
"items": { "type": "ref", "ref": "sh.comet.v0.feed.defs#tag" }
49
-
},
50
-
"link": {
51
-
"type": "ref",
52
-
"ref": "sh.comet.v0.feed.defs#link"
53
-
},
54
-
"createdAt": {
55
-
"type": "string",
56
-
"format": "datetime",
57
-
"description": "Timestamp for when the track was originally created/released."
58
-
}
59
-
}
60
-
}
61
-
},
62
-
"view": {
63
-
"type": "object",
64
-
"required": ["uri", "cid", "author", "audio", "record", "indexedAt"],
65
-
"properties": {
66
-
"uri": { "type": "string", "format": "at-uri" },
67
-
"cid": { "type": "string", "format": "cid" },
68
-
"author": { "type": "ref", "ref": "TODO" },
69
-
"audio": {
70
-
"type": "string",
71
-
"format": "uri",
72
-
"description": "URL pointing to where the audio data for the track can be fetched. May be re-encoded from the original blob."
73
-
},
74
-
"image": {
75
-
"type": "string",
76
-
"format": "uri",
77
-
"description": "URL pointing to where the image for the track can be fetched."
78
-
},
79
-
"record": {
80
-
"type": "ref",
81
-
"ref": "#main"
82
-
},
83
-
"repostCount": { "type": "integer" },
84
-
"likeCount": { "type": "integer" },
85
-
"playCount": { "type": "integer" },
86
-
"indexedAt": { "type": "string", "format": "datetime" },
87
-
"viewer": { "type": "ref", "ref": "sh.comet.v0.feed.defs#viewerState" }
88
-
}
89
-
}
90
-
}
91
-
}
-51
packages/lexicons/src/sh/comet/v0/richtext/facets.json
-51
packages/lexicons/src/sh/comet/v0/richtext/facets.json
···
1
-
{
2
-
"lexicon": 1,
3
-
"id": "sh.comet.v0.richtext.facets",
4
-
"defs": {
5
-
"main": {
6
-
"type": "object",
7
-
"description": "Annotation of a sub-string within rich text.",
8
-
"required": ["index", "features"],
9
-
"properties": {
10
-
"index": { "type": "ref", "ref": "#byteSlice" },
11
-
"features": {
12
-
"type": "array",
13
-
"items": { "type": "union", "refs": ["#mention", "#link", "#tag"] }
14
-
}
15
-
}
16
-
},
17
-
"mention": {
18
-
"type": "object",
19
-
"description": "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID.",
20
-
"required": ["did"],
21
-
"properties": {
22
-
"did": { "type": "string", "format": "did" }
23
-
}
24
-
},
25
-
"link": {
26
-
"type": "object",
27
-
"description": "Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL.",
28
-
"required": ["uri"],
29
-
"properties": {
30
-
"uri": { "type": "string", "format": "uri" }
31
-
}
32
-
},
33
-
"tag": {
34
-
"type": "object",
35
-
"description": "Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags').",
36
-
"required": ["tag"],
37
-
"properties": {
38
-
"tag": { "type": "ref", "ref": "sh.comet.v0.feed.defs#tag" }
39
-
}
40
-
},
41
-
"byteSlice": {
42
-
"type": "object",
43
-
"description": "Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets.",
44
-
"required": ["byteStart", "byteEnd"],
45
-
"properties": {
46
-
"byteStart": { "type": "integer", "minimum": 0 },
47
-
"byteEnd": { "type": "integer", "minimum": 0 }
48
-
}
49
-
}
50
-
}
51
-
}
-28
packages/lexicons/tsconfig.json
-28
packages/lexicons/tsconfig.json
···
1
-
{
2
-
"compilerOptions": {
3
-
// Environment setup & latest features
4
-
"lib": ["ESNext"],
5
-
"target": "ESNext",
6
-
"module": "ESNext",
7
-
"moduleDetection": "force",
8
-
"jsx": "react-jsx",
9
-
"allowJs": true,
10
-
11
-
// Bundler mode
12
-
"moduleResolution": "bundler",
13
-
"allowImportingTsExtensions": true,
14
-
"verbatimModuleSyntax": true,
15
-
"noEmit": true,
16
-
17
-
// Best practices
18
-
"strict": true,
19
-
"skipLibCheck": true,
20
-
"noFallthroughCasesInSwitch": true,
21
-
"noUncheckedIndexedAccess": true,
22
-
23
-
// Some stricter flags (disabled by default)
24
-
"noUnusedLocals": false,
25
-
"noUnusedParameters": false,
26
-
"noPropertyAccessFromIndexSignature": false
27
-
}
28
-
}
+705
pnpm-lock.yaml
+705
pnpm-lock.yaml
···
1
+
lockfileVersion: '9.0'
2
+
3
+
settings:
4
+
autoInstallPeers: true
5
+
excludeLinksFromLockfile: false
6
+
7
+
importers:
8
+
9
+
.:
10
+
dependencies:
11
+
'@fontsource-variable/work-sans':
12
+
specifier: ^5.2.8
13
+
version: 5.2.8
14
+
devDependencies:
15
+
'@tailwindcss/cli':
16
+
specifier: ^4.1.17
17
+
version: 4.1.17
18
+
prettier:
19
+
specifier: ^3.5.3
20
+
version: 3.7.1
21
+
prettier-plugin-tailwindcss:
22
+
specifier: ^0.6.11
23
+
version: 0.6.14(prettier@3.7.1)
24
+
tailwindcss:
25
+
specifier: ^4.1.17
26
+
version: 4.1.17
27
+
typescript:
28
+
specifier: ^5
29
+
version: 5.9.3
30
+
31
+
packages:
32
+
33
+
'@fontsource-variable/work-sans@5.2.8':
34
+
resolution: {integrity: sha512-8uWtTt0/B5NxGie9xUVD5y5Ch4Q+Hy7kFYKtUpwYbzSAgJEoaMxT8rMnfnK7zfAYSLC8GnGO1/tXrFtKIYYQVQ==}
35
+
36
+
'@jridgewell/gen-mapping@0.3.13':
37
+
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
38
+
39
+
'@jridgewell/remapping@2.3.5':
40
+
resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
41
+
42
+
'@jridgewell/resolve-uri@3.1.2':
43
+
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
44
+
engines: {node: '>=6.0.0'}
45
+
46
+
'@jridgewell/sourcemap-codec@1.5.5':
47
+
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
48
+
49
+
'@jridgewell/trace-mapping@0.3.31':
50
+
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
51
+
52
+
'@parcel/watcher-android-arm64@2.5.1':
53
+
resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
54
+
engines: {node: '>= 10.0.0'}
55
+
cpu: [arm64]
56
+
os: [android]
57
+
58
+
'@parcel/watcher-darwin-arm64@2.5.1':
59
+
resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
60
+
engines: {node: '>= 10.0.0'}
61
+
cpu: [arm64]
62
+
os: [darwin]
63
+
64
+
'@parcel/watcher-darwin-x64@2.5.1':
65
+
resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
66
+
engines: {node: '>= 10.0.0'}
67
+
cpu: [x64]
68
+
os: [darwin]
69
+
70
+
'@parcel/watcher-freebsd-x64@2.5.1':
71
+
resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==}
72
+
engines: {node: '>= 10.0.0'}
73
+
cpu: [x64]
74
+
os: [freebsd]
75
+
76
+
'@parcel/watcher-linux-arm-glibc@2.5.1':
77
+
resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==}
78
+
engines: {node: '>= 10.0.0'}
79
+
cpu: [arm]
80
+
os: [linux]
81
+
82
+
'@parcel/watcher-linux-arm-musl@2.5.1':
83
+
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
84
+
engines: {node: '>= 10.0.0'}
85
+
cpu: [arm]
86
+
os: [linux]
87
+
88
+
'@parcel/watcher-linux-arm64-glibc@2.5.1':
89
+
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
90
+
engines: {node: '>= 10.0.0'}
91
+
cpu: [arm64]
92
+
os: [linux]
93
+
94
+
'@parcel/watcher-linux-arm64-musl@2.5.1':
95
+
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
96
+
engines: {node: '>= 10.0.0'}
97
+
cpu: [arm64]
98
+
os: [linux]
99
+
100
+
'@parcel/watcher-linux-x64-glibc@2.5.1':
101
+
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
102
+
engines: {node: '>= 10.0.0'}
103
+
cpu: [x64]
104
+
os: [linux]
105
+
106
+
'@parcel/watcher-linux-x64-musl@2.5.1':
107
+
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
108
+
engines: {node: '>= 10.0.0'}
109
+
cpu: [x64]
110
+
os: [linux]
111
+
112
+
'@parcel/watcher-win32-arm64@2.5.1':
113
+
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
114
+
engines: {node: '>= 10.0.0'}
115
+
cpu: [arm64]
116
+
os: [win32]
117
+
118
+
'@parcel/watcher-win32-ia32@2.5.1':
119
+
resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==}
120
+
engines: {node: '>= 10.0.0'}
121
+
cpu: [ia32]
122
+
os: [win32]
123
+
124
+
'@parcel/watcher-win32-x64@2.5.1':
125
+
resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
126
+
engines: {node: '>= 10.0.0'}
127
+
cpu: [x64]
128
+
os: [win32]
129
+
130
+
'@parcel/watcher@2.5.1':
131
+
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
132
+
engines: {node: '>= 10.0.0'}
133
+
134
+
'@tailwindcss/cli@4.1.17':
135
+
resolution: {integrity: sha512-jUIxcyUNlCC2aNPnyPEWU/L2/ik3pB4fF3auKGXr8AvN3T3OFESVctFKOBoPZQaZJIeUpPn1uCLp0MRxuek8gg==}
136
+
hasBin: true
137
+
138
+
'@tailwindcss/node@4.1.17':
139
+
resolution: {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==}
140
+
141
+
'@tailwindcss/oxide-android-arm64@4.1.17':
142
+
resolution: {integrity: sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==}
143
+
engines: {node: '>= 10'}
144
+
cpu: [arm64]
145
+
os: [android]
146
+
147
+
'@tailwindcss/oxide-darwin-arm64@4.1.17':
148
+
resolution: {integrity: sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==}
149
+
engines: {node: '>= 10'}
150
+
cpu: [arm64]
151
+
os: [darwin]
152
+
153
+
'@tailwindcss/oxide-darwin-x64@4.1.17':
154
+
resolution: {integrity: sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==}
155
+
engines: {node: '>= 10'}
156
+
cpu: [x64]
157
+
os: [darwin]
158
+
159
+
'@tailwindcss/oxide-freebsd-x64@4.1.17':
160
+
resolution: {integrity: sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==}
161
+
engines: {node: '>= 10'}
162
+
cpu: [x64]
163
+
os: [freebsd]
164
+
165
+
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17':
166
+
resolution: {integrity: sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==}
167
+
engines: {node: '>= 10'}
168
+
cpu: [arm]
169
+
os: [linux]
170
+
171
+
'@tailwindcss/oxide-linux-arm64-gnu@4.1.17':
172
+
resolution: {integrity: sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==}
173
+
engines: {node: '>= 10'}
174
+
cpu: [arm64]
175
+
os: [linux]
176
+
177
+
'@tailwindcss/oxide-linux-arm64-musl@4.1.17':
178
+
resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==}
179
+
engines: {node: '>= 10'}
180
+
cpu: [arm64]
181
+
os: [linux]
182
+
183
+
'@tailwindcss/oxide-linux-x64-gnu@4.1.17':
184
+
resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==}
185
+
engines: {node: '>= 10'}
186
+
cpu: [x64]
187
+
os: [linux]
188
+
189
+
'@tailwindcss/oxide-linux-x64-musl@4.1.17':
190
+
resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==}
191
+
engines: {node: '>= 10'}
192
+
cpu: [x64]
193
+
os: [linux]
194
+
195
+
'@tailwindcss/oxide-wasm32-wasi@4.1.17':
196
+
resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==}
197
+
engines: {node: '>=14.0.0'}
198
+
cpu: [wasm32]
199
+
bundledDependencies:
200
+
- '@napi-rs/wasm-runtime'
201
+
- '@emnapi/core'
202
+
- '@emnapi/runtime'
203
+
- '@tybys/wasm-util'
204
+
- '@emnapi/wasi-threads'
205
+
- tslib
206
+
207
+
'@tailwindcss/oxide-win32-arm64-msvc@4.1.17':
208
+
resolution: {integrity: sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==}
209
+
engines: {node: '>= 10'}
210
+
cpu: [arm64]
211
+
os: [win32]
212
+
213
+
'@tailwindcss/oxide-win32-x64-msvc@4.1.17':
214
+
resolution: {integrity: sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==}
215
+
engines: {node: '>= 10'}
216
+
cpu: [x64]
217
+
os: [win32]
218
+
219
+
'@tailwindcss/oxide@4.1.17':
220
+
resolution: {integrity: sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==}
221
+
engines: {node: '>= 10'}
222
+
223
+
braces@3.0.3:
224
+
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
225
+
engines: {node: '>=8'}
226
+
227
+
detect-libc@1.0.3:
228
+
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
229
+
engines: {node: '>=0.10'}
230
+
hasBin: true
231
+
232
+
detect-libc@2.1.2:
233
+
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
234
+
engines: {node: '>=8'}
235
+
236
+
enhanced-resolve@5.18.3:
237
+
resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
238
+
engines: {node: '>=10.13.0'}
239
+
240
+
fill-range@7.1.1:
241
+
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
242
+
engines: {node: '>=8'}
243
+
244
+
graceful-fs@4.2.11:
245
+
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
246
+
247
+
is-extglob@2.1.1:
248
+
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
249
+
engines: {node: '>=0.10.0'}
250
+
251
+
is-glob@4.0.3:
252
+
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
253
+
engines: {node: '>=0.10.0'}
254
+
255
+
is-number@7.0.0:
256
+
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
257
+
engines: {node: '>=0.12.0'}
258
+
259
+
jiti@2.6.1:
260
+
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
261
+
hasBin: true
262
+
263
+
lightningcss-android-arm64@1.30.2:
264
+
resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
265
+
engines: {node: '>= 12.0.0'}
266
+
cpu: [arm64]
267
+
os: [android]
268
+
269
+
lightningcss-darwin-arm64@1.30.2:
270
+
resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==}
271
+
engines: {node: '>= 12.0.0'}
272
+
cpu: [arm64]
273
+
os: [darwin]
274
+
275
+
lightningcss-darwin-x64@1.30.2:
276
+
resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==}
277
+
engines: {node: '>= 12.0.0'}
278
+
cpu: [x64]
279
+
os: [darwin]
280
+
281
+
lightningcss-freebsd-x64@1.30.2:
282
+
resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==}
283
+
engines: {node: '>= 12.0.0'}
284
+
cpu: [x64]
285
+
os: [freebsd]
286
+
287
+
lightningcss-linux-arm-gnueabihf@1.30.2:
288
+
resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==}
289
+
engines: {node: '>= 12.0.0'}
290
+
cpu: [arm]
291
+
os: [linux]
292
+
293
+
lightningcss-linux-arm64-gnu@1.30.2:
294
+
resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
295
+
engines: {node: '>= 12.0.0'}
296
+
cpu: [arm64]
297
+
os: [linux]
298
+
299
+
lightningcss-linux-arm64-musl@1.30.2:
300
+
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
301
+
engines: {node: '>= 12.0.0'}
302
+
cpu: [arm64]
303
+
os: [linux]
304
+
305
+
lightningcss-linux-x64-gnu@1.30.2:
306
+
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
307
+
engines: {node: '>= 12.0.0'}
308
+
cpu: [x64]
309
+
os: [linux]
310
+
311
+
lightningcss-linux-x64-musl@1.30.2:
312
+
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
313
+
engines: {node: '>= 12.0.0'}
314
+
cpu: [x64]
315
+
os: [linux]
316
+
317
+
lightningcss-win32-arm64-msvc@1.30.2:
318
+
resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
319
+
engines: {node: '>= 12.0.0'}
320
+
cpu: [arm64]
321
+
os: [win32]
322
+
323
+
lightningcss-win32-x64-msvc@1.30.2:
324
+
resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==}
325
+
engines: {node: '>= 12.0.0'}
326
+
cpu: [x64]
327
+
os: [win32]
328
+
329
+
lightningcss@1.30.2:
330
+
resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
331
+
engines: {node: '>= 12.0.0'}
332
+
333
+
magic-string@0.30.21:
334
+
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
335
+
336
+
micromatch@4.0.8:
337
+
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
338
+
engines: {node: '>=8.6'}
339
+
340
+
mri@1.2.0:
341
+
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
342
+
engines: {node: '>=4'}
343
+
344
+
node-addon-api@7.1.1:
345
+
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
346
+
347
+
picocolors@1.1.1:
348
+
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
349
+
350
+
picomatch@2.3.1:
351
+
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
352
+
engines: {node: '>=8.6'}
353
+
354
+
prettier-plugin-tailwindcss@0.6.14:
355
+
resolution: {integrity: sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==}
356
+
engines: {node: '>=14.21.3'}
357
+
peerDependencies:
358
+
'@ianvs/prettier-plugin-sort-imports': '*'
359
+
'@prettier/plugin-hermes': '*'
360
+
'@prettier/plugin-oxc': '*'
361
+
'@prettier/plugin-pug': '*'
362
+
'@shopify/prettier-plugin-liquid': '*'
363
+
'@trivago/prettier-plugin-sort-imports': '*'
364
+
'@zackad/prettier-plugin-twig': '*'
365
+
prettier: ^3.0
366
+
prettier-plugin-astro: '*'
367
+
prettier-plugin-css-order: '*'
368
+
prettier-plugin-import-sort: '*'
369
+
prettier-plugin-jsdoc: '*'
370
+
prettier-plugin-marko: '*'
371
+
prettier-plugin-multiline-arrays: '*'
372
+
prettier-plugin-organize-attributes: '*'
373
+
prettier-plugin-organize-imports: '*'
374
+
prettier-plugin-sort-imports: '*'
375
+
prettier-plugin-style-order: '*'
376
+
prettier-plugin-svelte: '*'
377
+
peerDependenciesMeta:
378
+
'@ianvs/prettier-plugin-sort-imports':
379
+
optional: true
380
+
'@prettier/plugin-hermes':
381
+
optional: true
382
+
'@prettier/plugin-oxc':
383
+
optional: true
384
+
'@prettier/plugin-pug':
385
+
optional: true
386
+
'@shopify/prettier-plugin-liquid':
387
+
optional: true
388
+
'@trivago/prettier-plugin-sort-imports':
389
+
optional: true
390
+
'@zackad/prettier-plugin-twig':
391
+
optional: true
392
+
prettier-plugin-astro:
393
+
optional: true
394
+
prettier-plugin-css-order:
395
+
optional: true
396
+
prettier-plugin-import-sort:
397
+
optional: true
398
+
prettier-plugin-jsdoc:
399
+
optional: true
400
+
prettier-plugin-marko:
401
+
optional: true
402
+
prettier-plugin-multiline-arrays:
403
+
optional: true
404
+
prettier-plugin-organize-attributes:
405
+
optional: true
406
+
prettier-plugin-organize-imports:
407
+
optional: true
408
+
prettier-plugin-sort-imports:
409
+
optional: true
410
+
prettier-plugin-style-order:
411
+
optional: true
412
+
prettier-plugin-svelte:
413
+
optional: true
414
+
415
+
prettier@3.7.1:
416
+
resolution: {integrity: sha512-RWKXE4qB3u5Z6yz7omJkjWwmTfLdcbv44jUVHC5NpfXwFGzvpQM798FGv/6WNK879tc+Cn0AAyherCl1KjbyZQ==}
417
+
engines: {node: '>=14'}
418
+
hasBin: true
419
+
420
+
source-map-js@1.2.1:
421
+
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
422
+
engines: {node: '>=0.10.0'}
423
+
424
+
tailwindcss@4.1.17:
425
+
resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==}
426
+
427
+
tapable@2.3.0:
428
+
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
429
+
engines: {node: '>=6'}
430
+
431
+
to-regex-range@5.0.1:
432
+
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
433
+
engines: {node: '>=8.0'}
434
+
435
+
typescript@5.9.3:
436
+
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
437
+
engines: {node: '>=14.17'}
438
+
hasBin: true
439
+
440
+
snapshots:
441
+
442
+
'@fontsource-variable/work-sans@5.2.8': {}
443
+
444
+
'@jridgewell/gen-mapping@0.3.13':
445
+
dependencies:
446
+
'@jridgewell/sourcemap-codec': 1.5.5
447
+
'@jridgewell/trace-mapping': 0.3.31
448
+
449
+
'@jridgewell/remapping@2.3.5':
450
+
dependencies:
451
+
'@jridgewell/gen-mapping': 0.3.13
452
+
'@jridgewell/trace-mapping': 0.3.31
453
+
454
+
'@jridgewell/resolve-uri@3.1.2': {}
455
+
456
+
'@jridgewell/sourcemap-codec@1.5.5': {}
457
+
458
+
'@jridgewell/trace-mapping@0.3.31':
459
+
dependencies:
460
+
'@jridgewell/resolve-uri': 3.1.2
461
+
'@jridgewell/sourcemap-codec': 1.5.5
462
+
463
+
'@parcel/watcher-android-arm64@2.5.1':
464
+
optional: true
465
+
466
+
'@parcel/watcher-darwin-arm64@2.5.1':
467
+
optional: true
468
+
469
+
'@parcel/watcher-darwin-x64@2.5.1':
470
+
optional: true
471
+
472
+
'@parcel/watcher-freebsd-x64@2.5.1':
473
+
optional: true
474
+
475
+
'@parcel/watcher-linux-arm-glibc@2.5.1':
476
+
optional: true
477
+
478
+
'@parcel/watcher-linux-arm-musl@2.5.1':
479
+
optional: true
480
+
481
+
'@parcel/watcher-linux-arm64-glibc@2.5.1':
482
+
optional: true
483
+
484
+
'@parcel/watcher-linux-arm64-musl@2.5.1':
485
+
optional: true
486
+
487
+
'@parcel/watcher-linux-x64-glibc@2.5.1':
488
+
optional: true
489
+
490
+
'@parcel/watcher-linux-x64-musl@2.5.1':
491
+
optional: true
492
+
493
+
'@parcel/watcher-win32-arm64@2.5.1':
494
+
optional: true
495
+
496
+
'@parcel/watcher-win32-ia32@2.5.1':
497
+
optional: true
498
+
499
+
'@parcel/watcher-win32-x64@2.5.1':
500
+
optional: true
501
+
502
+
'@parcel/watcher@2.5.1':
503
+
dependencies:
504
+
detect-libc: 1.0.3
505
+
is-glob: 4.0.3
506
+
micromatch: 4.0.8
507
+
node-addon-api: 7.1.1
508
+
optionalDependencies:
509
+
'@parcel/watcher-android-arm64': 2.5.1
510
+
'@parcel/watcher-darwin-arm64': 2.5.1
511
+
'@parcel/watcher-darwin-x64': 2.5.1
512
+
'@parcel/watcher-freebsd-x64': 2.5.1
513
+
'@parcel/watcher-linux-arm-glibc': 2.5.1
514
+
'@parcel/watcher-linux-arm-musl': 2.5.1
515
+
'@parcel/watcher-linux-arm64-glibc': 2.5.1
516
+
'@parcel/watcher-linux-arm64-musl': 2.5.1
517
+
'@parcel/watcher-linux-x64-glibc': 2.5.1
518
+
'@parcel/watcher-linux-x64-musl': 2.5.1
519
+
'@parcel/watcher-win32-arm64': 2.5.1
520
+
'@parcel/watcher-win32-ia32': 2.5.1
521
+
'@parcel/watcher-win32-x64': 2.5.1
522
+
523
+
'@tailwindcss/cli@4.1.17':
524
+
dependencies:
525
+
'@parcel/watcher': 2.5.1
526
+
'@tailwindcss/node': 4.1.17
527
+
'@tailwindcss/oxide': 4.1.17
528
+
enhanced-resolve: 5.18.3
529
+
mri: 1.2.0
530
+
picocolors: 1.1.1
531
+
tailwindcss: 4.1.17
532
+
533
+
'@tailwindcss/node@4.1.17':
534
+
dependencies:
535
+
'@jridgewell/remapping': 2.3.5
536
+
enhanced-resolve: 5.18.3
537
+
jiti: 2.6.1
538
+
lightningcss: 1.30.2
539
+
magic-string: 0.30.21
540
+
source-map-js: 1.2.1
541
+
tailwindcss: 4.1.17
542
+
543
+
'@tailwindcss/oxide-android-arm64@4.1.17':
544
+
optional: true
545
+
546
+
'@tailwindcss/oxide-darwin-arm64@4.1.17':
547
+
optional: true
548
+
549
+
'@tailwindcss/oxide-darwin-x64@4.1.17':
550
+
optional: true
551
+
552
+
'@tailwindcss/oxide-freebsd-x64@4.1.17':
553
+
optional: true
554
+
555
+
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17':
556
+
optional: true
557
+
558
+
'@tailwindcss/oxide-linux-arm64-gnu@4.1.17':
559
+
optional: true
560
+
561
+
'@tailwindcss/oxide-linux-arm64-musl@4.1.17':
562
+
optional: true
563
+
564
+
'@tailwindcss/oxide-linux-x64-gnu@4.1.17':
565
+
optional: true
566
+
567
+
'@tailwindcss/oxide-linux-x64-musl@4.1.17':
568
+
optional: true
569
+
570
+
'@tailwindcss/oxide-wasm32-wasi@4.1.17':
571
+
optional: true
572
+
573
+
'@tailwindcss/oxide-win32-arm64-msvc@4.1.17':
574
+
optional: true
575
+
576
+
'@tailwindcss/oxide-win32-x64-msvc@4.1.17':
577
+
optional: true
578
+
579
+
'@tailwindcss/oxide@4.1.17':
580
+
optionalDependencies:
581
+
'@tailwindcss/oxide-android-arm64': 4.1.17
582
+
'@tailwindcss/oxide-darwin-arm64': 4.1.17
583
+
'@tailwindcss/oxide-darwin-x64': 4.1.17
584
+
'@tailwindcss/oxide-freebsd-x64': 4.1.17
585
+
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.17
586
+
'@tailwindcss/oxide-linux-arm64-gnu': 4.1.17
587
+
'@tailwindcss/oxide-linux-arm64-musl': 4.1.17
588
+
'@tailwindcss/oxide-linux-x64-gnu': 4.1.17
589
+
'@tailwindcss/oxide-linux-x64-musl': 4.1.17
590
+
'@tailwindcss/oxide-wasm32-wasi': 4.1.17
591
+
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.17
592
+
'@tailwindcss/oxide-win32-x64-msvc': 4.1.17
593
+
594
+
braces@3.0.3:
595
+
dependencies:
596
+
fill-range: 7.1.1
597
+
598
+
detect-libc@1.0.3: {}
599
+
600
+
detect-libc@2.1.2: {}
601
+
602
+
enhanced-resolve@5.18.3:
603
+
dependencies:
604
+
graceful-fs: 4.2.11
605
+
tapable: 2.3.0
606
+
607
+
fill-range@7.1.1:
608
+
dependencies:
609
+
to-regex-range: 5.0.1
610
+
611
+
graceful-fs@4.2.11: {}
612
+
613
+
is-extglob@2.1.1: {}
614
+
615
+
is-glob@4.0.3:
616
+
dependencies:
617
+
is-extglob: 2.1.1
618
+
619
+
is-number@7.0.0: {}
620
+
621
+
jiti@2.6.1: {}
622
+
623
+
lightningcss-android-arm64@1.30.2:
624
+
optional: true
625
+
626
+
lightningcss-darwin-arm64@1.30.2:
627
+
optional: true
628
+
629
+
lightningcss-darwin-x64@1.30.2:
630
+
optional: true
631
+
632
+
lightningcss-freebsd-x64@1.30.2:
633
+
optional: true
634
+
635
+
lightningcss-linux-arm-gnueabihf@1.30.2:
636
+
optional: true
637
+
638
+
lightningcss-linux-arm64-gnu@1.30.2:
639
+
optional: true
640
+
641
+
lightningcss-linux-arm64-musl@1.30.2:
642
+
optional: true
643
+
644
+
lightningcss-linux-x64-gnu@1.30.2:
645
+
optional: true
646
+
647
+
lightningcss-linux-x64-musl@1.30.2:
648
+
optional: true
649
+
650
+
lightningcss-win32-arm64-msvc@1.30.2:
651
+
optional: true
652
+
653
+
lightningcss-win32-x64-msvc@1.30.2:
654
+
optional: true
655
+
656
+
lightningcss@1.30.2:
657
+
dependencies:
658
+
detect-libc: 2.1.2
659
+
optionalDependencies:
660
+
lightningcss-android-arm64: 1.30.2
661
+
lightningcss-darwin-arm64: 1.30.2
662
+
lightningcss-darwin-x64: 1.30.2
663
+
lightningcss-freebsd-x64: 1.30.2
664
+
lightningcss-linux-arm-gnueabihf: 1.30.2
665
+
lightningcss-linux-arm64-gnu: 1.30.2
666
+
lightningcss-linux-arm64-musl: 1.30.2
667
+
lightningcss-linux-x64-gnu: 1.30.2
668
+
lightningcss-linux-x64-musl: 1.30.2
669
+
lightningcss-win32-arm64-msvc: 1.30.2
670
+
lightningcss-win32-x64-msvc: 1.30.2
671
+
672
+
magic-string@0.30.21:
673
+
dependencies:
674
+
'@jridgewell/sourcemap-codec': 1.5.5
675
+
676
+
micromatch@4.0.8:
677
+
dependencies:
678
+
braces: 3.0.3
679
+
picomatch: 2.3.1
680
+
681
+
mri@1.2.0: {}
682
+
683
+
node-addon-api@7.1.1: {}
684
+
685
+
picocolors@1.1.1: {}
686
+
687
+
picomatch@2.3.1: {}
688
+
689
+
prettier-plugin-tailwindcss@0.6.14(prettier@3.7.1):
690
+
dependencies:
691
+
prettier: 3.7.1
692
+
693
+
prettier@3.7.1: {}
694
+
695
+
source-map-js@1.2.1: {}
696
+
697
+
tailwindcss@4.1.17: {}
698
+
699
+
tapable@2.3.0: {}
700
+
701
+
to-regex-range@5.0.1:
702
+
dependencies:
703
+
is-number: 7.0.0
704
+
705
+
typescript@5.9.3: {}
+112
priv/gettext/en/LC_MESSAGES/errors.po
+112
priv/gettext/en/LC_MESSAGES/errors.po
···
1
+
## `msgid`s in this file come from POT (.pot) files.
2
+
##
3
+
## Do not add, change, or remove `msgid`s manually here as
4
+
## they're tied to the ones in the corresponding POT file
5
+
## (with the same domain).
6
+
##
7
+
## Use `mix gettext.extract --merge` or `mix gettext.merge`
8
+
## to merge POT files into PO files.
9
+
msgid ""
10
+
msgstr ""
11
+
"Language: en\n"
12
+
13
+
## From Ecto.Changeset.cast/4
14
+
msgid "can't be blank"
15
+
msgstr ""
16
+
17
+
## From Ecto.Changeset.unique_constraint/3
18
+
msgid "has already been taken"
19
+
msgstr ""
20
+
21
+
## From Ecto.Changeset.put_change/3
22
+
msgid "is invalid"
23
+
msgstr ""
24
+
25
+
## From Ecto.Changeset.validate_acceptance/3
26
+
msgid "must be accepted"
27
+
msgstr ""
28
+
29
+
## From Ecto.Changeset.validate_format/3
30
+
msgid "has invalid format"
31
+
msgstr ""
32
+
33
+
## From Ecto.Changeset.validate_subset/3
34
+
msgid "has an invalid entry"
35
+
msgstr ""
36
+
37
+
## From Ecto.Changeset.validate_exclusion/3
38
+
msgid "is reserved"
39
+
msgstr ""
40
+
41
+
## From Ecto.Changeset.validate_confirmation/3
42
+
msgid "does not match confirmation"
43
+
msgstr ""
44
+
45
+
## From Ecto.Changeset.no_assoc_constraint/3
46
+
msgid "is still associated with this entry"
47
+
msgstr ""
48
+
49
+
msgid "are still associated with this entry"
50
+
msgstr ""
51
+
52
+
## From Ecto.Changeset.validate_length/3
53
+
msgid "should have %{count} item(s)"
54
+
msgid_plural "should have %{count} item(s)"
55
+
msgstr[0] ""
56
+
msgstr[1] ""
57
+
58
+
msgid "should be %{count} character(s)"
59
+
msgid_plural "should be %{count} character(s)"
60
+
msgstr[0] ""
61
+
msgstr[1] ""
62
+
63
+
msgid "should be %{count} byte(s)"
64
+
msgid_plural "should be %{count} byte(s)"
65
+
msgstr[0] ""
66
+
msgstr[1] ""
67
+
68
+
msgid "should have at least %{count} item(s)"
69
+
msgid_plural "should have at least %{count} item(s)"
70
+
msgstr[0] ""
71
+
msgstr[1] ""
72
+
73
+
msgid "should be at least %{count} character(s)"
74
+
msgid_plural "should be at least %{count} character(s)"
75
+
msgstr[0] ""
76
+
msgstr[1] ""
77
+
78
+
msgid "should be at least %{count} byte(s)"
79
+
msgid_plural "should be at least %{count} byte(s)"
80
+
msgstr[0] ""
81
+
msgstr[1] ""
82
+
83
+
msgid "should have at most %{count} item(s)"
84
+
msgid_plural "should have at most %{count} item(s)"
85
+
msgstr[0] ""
86
+
msgstr[1] ""
87
+
88
+
msgid "should be at most %{count} character(s)"
89
+
msgid_plural "should be at most %{count} character(s)"
90
+
msgstr[0] ""
91
+
msgstr[1] ""
92
+
93
+
msgid "should be at most %{count} byte(s)"
94
+
msgid_plural "should be at most %{count} byte(s)"
95
+
msgstr[0] ""
96
+
msgstr[1] ""
97
+
98
+
## From Ecto.Changeset.validate_number/3
99
+
msgid "must be less than %{number}"
100
+
msgstr ""
101
+
102
+
msgid "must be greater than %{number}"
103
+
msgstr ""
104
+
105
+
msgid "must be less than or equal to %{number}"
106
+
msgstr ""
107
+
108
+
msgid "must be greater than or equal to %{number}"
109
+
msgstr ""
110
+
111
+
msgid "must be equal to %{number}"
112
+
msgstr ""
+109
priv/gettext/errors.pot
+109
priv/gettext/errors.pot
···
1
+
## This is a PO Template file.
2
+
##
3
+
## `msgid`s here are often extracted from source code.
4
+
## Add new translations manually only if they're dynamic
5
+
## translations that can't be statically extracted.
6
+
##
7
+
## Run `mix gettext.extract` to bring this file up to
8
+
## date. Leave `msgstr`s empty as changing them here has no
9
+
## effect: edit them in PO (`.po`) files instead.
10
+
## From Ecto.Changeset.cast/4
11
+
msgid "can't be blank"
12
+
msgstr ""
13
+
14
+
## From Ecto.Changeset.unique_constraint/3
15
+
msgid "has already been taken"
16
+
msgstr ""
17
+
18
+
## From Ecto.Changeset.put_change/3
19
+
msgid "is invalid"
20
+
msgstr ""
21
+
22
+
## From Ecto.Changeset.validate_acceptance/3
23
+
msgid "must be accepted"
24
+
msgstr ""
25
+
26
+
## From Ecto.Changeset.validate_format/3
27
+
msgid "has invalid format"
28
+
msgstr ""
29
+
30
+
## From Ecto.Changeset.validate_subset/3
31
+
msgid "has an invalid entry"
32
+
msgstr ""
33
+
34
+
## From Ecto.Changeset.validate_exclusion/3
35
+
msgid "is reserved"
36
+
msgstr ""
37
+
38
+
## From Ecto.Changeset.validate_confirmation/3
39
+
msgid "does not match confirmation"
40
+
msgstr ""
41
+
42
+
## From Ecto.Changeset.no_assoc_constraint/3
43
+
msgid "is still associated with this entry"
44
+
msgstr ""
45
+
46
+
msgid "are still associated with this entry"
47
+
msgstr ""
48
+
49
+
## From Ecto.Changeset.validate_length/3
50
+
msgid "should have %{count} item(s)"
51
+
msgid_plural "should have %{count} item(s)"
52
+
msgstr[0] ""
53
+
msgstr[1] ""
54
+
55
+
msgid "should be %{count} character(s)"
56
+
msgid_plural "should be %{count} character(s)"
57
+
msgstr[0] ""
58
+
msgstr[1] ""
59
+
60
+
msgid "should be %{count} byte(s)"
61
+
msgid_plural "should be %{count} byte(s)"
62
+
msgstr[0] ""
63
+
msgstr[1] ""
64
+
65
+
msgid "should have at least %{count} item(s)"
66
+
msgid_plural "should have at least %{count} item(s)"
67
+
msgstr[0] ""
68
+
msgstr[1] ""
69
+
70
+
msgid "should be at least %{count} character(s)"
71
+
msgid_plural "should be at least %{count} character(s)"
72
+
msgstr[0] ""
73
+
msgstr[1] ""
74
+
75
+
msgid "should be at least %{count} byte(s)"
76
+
msgid_plural "should be at least %{count} byte(s)"
77
+
msgstr[0] ""
78
+
msgstr[1] ""
79
+
80
+
msgid "should have at most %{count} item(s)"
81
+
msgid_plural "should have at most %{count} item(s)"
82
+
msgstr[0] ""
83
+
msgstr[1] ""
84
+
85
+
msgid "should be at most %{count} character(s)"
86
+
msgid_plural "should be at most %{count} character(s)"
87
+
msgstr[0] ""
88
+
msgstr[1] ""
89
+
90
+
msgid "should be at most %{count} byte(s)"
91
+
msgid_plural "should be at most %{count} byte(s)"
92
+
msgstr[0] ""
93
+
msgstr[1] ""
94
+
95
+
## From Ecto.Changeset.validate_number/3
96
+
msgid "must be less than %{number}"
97
+
msgstr ""
98
+
99
+
msgid "must be greater than %{number}"
100
+
msgstr ""
101
+
102
+
msgid "must be less than or equal to %{number}"
103
+
msgstr ""
104
+
105
+
msgid "must be greater than or equal to %{number}"
106
+
msgstr ""
107
+
108
+
msgid "must be equal to %{number}"
109
+
msgstr ""
+4
priv/repo/migrations/.formatter.exs
+4
priv/repo/migrations/.formatter.exs
+11
priv/repo/seeds.exs
+11
priv/repo/seeds.exs
···
1
+
# Script for populating the database. You can run it as:
2
+
#
3
+
# mix run priv/repo/seeds.exs
4
+
#
5
+
# Inside the script, you can read and write to any of your
6
+
# repositories directly:
7
+
#
8
+
# Comet.Repo.insert!(%Comet.SomeSchema{})
9
+
#
10
+
# We recommend using the bang functions (`insert!`, `update!`
11
+
# and so on) as they will fail if something goes wrong.
priv/static/favicon.ico
priv/static/favicon.ico
This is a binary file and will not be displayed.
+6
priv/static/images/logo.svg
+6
priv/static/images/logo.svg
···
1
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 71 48" fill="currentColor" aria-hidden="true">
2
+
<path
3
+
d="m26.371 33.477-.552-.1c-3.92-.729-6.397-3.1-7.57-6.829-.733-2.324.597-4.035 3.035-4.148 1.995-.092 3.362 1.055 4.57 2.39 1.557 1.72 2.984 3.558 4.514 5.305 2.202 2.515 4.797 4.134 8.347 3.634 3.183-.448 5.958-1.725 8.371-3.828.363-.316.761-.592 1.144-.886l-.241-.284c-2.027.63-4.093.841-6.205.735-3.195-.16-6.24-.828-8.964-2.582-2.486-1.601-4.319-3.746-5.19-6.611-.704-2.315.736-3.934 3.135-3.6.948.133 1.746.56 2.463 1.165.583.493 1.143 1.015 1.738 1.493 2.8 2.25 6.712 2.375 10.265-.068-5.842-.026-9.817-3.24-13.308-7.313-1.366-1.594-2.7-3.216-4.095-4.785-2.698-3.036-5.692-5.71-9.79-6.623C12.8-.623 7.745.14 2.893 2.361 1.926 2.804.997 3.319 0 4.149c.494 0 .763.006 1.032 0 2.446-.064 4.28 1.023 5.602 3.024.962 1.457 1.415 3.104 1.761 4.798.513 2.515.247 5.078.544 7.605.761 6.494 4.08 11.026 10.26 13.346 2.267.852 4.591 1.135 7.172.555ZM10.751 3.852c-.976.246-1.756-.148-2.56-.962 1.377-.343 2.592-.476 3.897-.528-.107.848-.607 1.306-1.336 1.49Zm32.002 37.924c-.085-.626-.62-.901-1.04-1.228-1.857-1.446-4.03-1.958-6.333-2-1.375-.026-2.735-.128-4.031-.61-.595-.22-1.26-.505-1.244-1.272.015-.78.693-1 1.31-1.184.505-.15 1.026-.247 1.6-.382-1.46-.936-2.886-1.065-4.787-.3-2.993 1.202-5.943 1.06-8.926-.017-1.684-.608-3.179-1.563-4.735-2.408l-.077.057c1.29 2.115 3.034 3.817 5.004 5.271 3.793 2.8 7.936 4.471 12.784 3.73A66.714 66.714 0 0 1 37 40.877c1.98-.16 3.866.398 5.753.899Zm-9.14-30.345c-.105-.076-.206-.266-.42-.069 1.745 2.36 3.985 4.098 6.683 5.193 4.354 1.767 8.773 2.07 13.293.51 3.51-1.21 6.033-.028 7.343 3.38.19-3.955-2.137-6.837-5.843-7.401-2.084-.318-4.01.373-5.962.94-5.434 1.575-10.485.798-15.094-2.553Zm27.085 15.425c.708.059 1.416.123 2.124.185-1.6-1.405-3.55-1.517-5.523-1.404-3.003.17-5.167 1.903-7.14 3.972-1.739 1.824-3.31 3.87-5.903 4.604.043.078.054.117.066.117.35.005.699.021 1.047.005 3.768-.17 7.317-.965 10.14-3.7.89-.86 1.685-1.817 2.544-2.71.716-.746 1.584-1.159 2.645-1.07Zm-8.753-4.67c-2.812.246-5.254 1.409-7.548 2.943-1.766 1.18-3.654 1.738-5.776 1.37-.374-.066-.75-.114-1.124-.17l-.013.156c.135.07.265.151.405.207.354.14.702.308 1.07.395 4.083.971 7.992.474 11.516-1.803 2.221-1.435 4.521-1.707 7.013-1.336.252.038.503.083.756.107.234.022.479.255.795.003-2.179-1.574-4.526-2.096-7.094-1.872Zm-10.049-9.544c1.475.051 2.943-.142 4.486-1.059-.452.04-.643.04-.827.076-2.126.424-4.033-.04-5.733-1.383-.623-.493-1.257-.974-1.889-1.457-2.503-1.914-5.374-2.555-8.514-2.5.05.154.054.26.108.315 3.417 3.455 7.371 5.836 12.369 6.008Zm24.727 17.731c-2.114-2.097-4.952-2.367-7.578-.537 1.738.078 3.043.632 4.101 1.728a13 13 0 0 0 1.182 1.106c1.6 1.29 4.311 1.352 5.896.155-1.861-.726-1.861-.726-3.601-2.452Zm-21.058 16.06c-1.858-3.46-4.981-4.24-8.59-4.008a9.667 9.667 0 0 1 2.977 1.39c.84.586 1.547 1.311 2.243 2.055 1.38 1.473 3.534 2.376 4.962 2.07-.656-.412-1.238-.848-1.592-1.507Zl-.006.006-.036-.004.021.018.012.053Za.127.127 0 0 0 .015.043c.005.008.038 0 .058-.002Zl-.008.01.005.026.024.014Z"
4
+
fill="#FD4F00"
5
+
/>
6
+
</svg>
+5
priv/static/robots.txt
+5
priv/static/robots.txt
+14
test/comet_web/controllers/error_html_test.exs
+14
test/comet_web/controllers/error_html_test.exs
···
1
+
defmodule CometWeb.ErrorHTMLTest do
2
+
use CometWeb.ConnCase, async: true
3
+
4
+
# Bring render_to_string/4 for testing custom views
5
+
import Phoenix.Template, only: [render_to_string: 4]
6
+
7
+
test "renders 404.html" do
8
+
assert render_to_string(CometWeb.ErrorHTML, "404", "html", []) == "Not Found"
9
+
end
10
+
11
+
test "renders 500.html" do
12
+
assert render_to_string(CometWeb.ErrorHTML, "500", "html", []) == "Internal Server Error"
13
+
end
14
+
end
+12
test/comet_web/controllers/error_json_test.exs
+12
test/comet_web/controllers/error_json_test.exs
···
1
+
defmodule CometWeb.ErrorJSONTest do
2
+
use CometWeb.ConnCase, async: true
3
+
4
+
test "renders 404" do
5
+
assert CometWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}}
6
+
end
7
+
8
+
test "renders 500" do
9
+
assert CometWeb.ErrorJSON.render("500.json", %{}) ==
10
+
%{errors: %{detail: "Internal Server Error"}}
11
+
end
12
+
end
+8
test/comet_web/controllers/page_controller_test.exs
+8
test/comet_web/controllers/page_controller_test.exs
+38
test/support/conn_case.ex
+38
test/support/conn_case.ex
···
1
+
defmodule CometWeb.ConnCase do
2
+
@moduledoc """
3
+
This module defines the test case to be used by
4
+
tests that require setting up a connection.
5
+
6
+
Such tests rely on `Phoenix.ConnTest` and also
7
+
import other functionality to make it easier
8
+
to build common data structures and query the data layer.
9
+
10
+
Finally, if the test case interacts with the database,
11
+
we enable the SQL sandbox, so changes done to the database
12
+
are reverted at the end of every test. If you are using
13
+
PostgreSQL, you can even run database tests asynchronously
14
+
by setting `use CometWeb.ConnCase, async: true`, although
15
+
this option is not recommended for other databases.
16
+
"""
17
+
18
+
use ExUnit.CaseTemplate
19
+
20
+
using do
21
+
quote do
22
+
# The default endpoint for testing
23
+
@endpoint CometWeb.Endpoint
24
+
25
+
use CometWeb, :verified_routes
26
+
27
+
# Import conveniences for testing with connections
28
+
import Plug.Conn
29
+
import Phoenix.ConnTest
30
+
import CometWeb.ConnCase
31
+
end
32
+
end
33
+
34
+
setup tags do
35
+
Comet.DataCase.setup_sandbox(tags)
36
+
{:ok, conn: Phoenix.ConnTest.build_conn()}
37
+
end
38
+
end
+58
test/support/data_case.ex
+58
test/support/data_case.ex
···
1
+
defmodule Comet.DataCase do
2
+
@moduledoc """
3
+
This module defines the setup for tests requiring
4
+
access to the application's data layer.
5
+
6
+
You may define functions here to be used as helpers in
7
+
your tests.
8
+
9
+
Finally, if the test case interacts with the database,
10
+
we enable the SQL sandbox, so changes done to the database
11
+
are reverted at the end of every test. If you are using
12
+
PostgreSQL, you can even run database tests asynchronously
13
+
by setting `use Comet.DataCase, async: true`, although
14
+
this option is not recommended for other databases.
15
+
"""
16
+
17
+
use ExUnit.CaseTemplate
18
+
19
+
using do
20
+
quote do
21
+
alias Comet.Repo
22
+
23
+
import Ecto
24
+
import Ecto.Changeset
25
+
import Ecto.Query
26
+
import Comet.DataCase
27
+
end
28
+
end
29
+
30
+
setup tags do
31
+
Comet.DataCase.setup_sandbox(tags)
32
+
:ok
33
+
end
34
+
35
+
@doc """
36
+
Sets up the sandbox based on the test tags.
37
+
"""
38
+
def setup_sandbox(tags) do
39
+
pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Comet.Repo, shared: not tags[:async])
40
+
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
41
+
end
42
+
43
+
@doc """
44
+
A helper that transforms changeset errors into a map of messages.
45
+
46
+
assert {:error, changeset} = Accounts.create_user(%{password: "short"})
47
+
assert "password is too short" in errors_on(changeset).password
48
+
assert %{password: ["password is too short"]} = errors_on(changeset)
49
+
50
+
"""
51
+
def errors_on(changeset) do
52
+
Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
53
+
Regex.replace(~r"%{(\w+)}", message, fn _, key ->
54
+
opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()
55
+
end)
56
+
end)
57
+
end
58
+
end