Giter Club home page Giter Club logo

lustre's People

Contributors

0xca551e avatar ahouseago avatar bgwdotdev avatar brettcannon avatar cdaringe avatar darenc avatar despairblue avatar donkeybanana avatar enoonan avatar erikareads avatar ffigiel avatar ghivert avatar giacomocavalieri avatar greenfork avatar hayleigh-dot-dev avatar ianmjones avatar jfmengels avatar jmpavlick avatar jollmar avatar jscearcy avatar keroami avatar kgroat avatar lpil avatar markholmes avatar maxdeviant avatar michaeljones avatar morucci avatar nerdyworm avatar nicd avatar xpressivecode avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lustre's Issues

✨ Serve nested modules that return Lustre applications with `lustre/try`.

The lustre/try preview server at the moment only serves the application from the main function in the module that corresponds to the project's name.

This is a sensible entry but for folks that want to experiment with an MPA setup it might be nice to detect and serve any routes that setup a Lustre application (or return an Element see: #28).

This is probably as much scope creep as we should tolerate for now, because ideally we want to push folks into either use vite or esbuild (through the cli) instead.

✨ Implement a `fragment` to contain multiple elements at once

In other frameworks it's often quite handy to be able to return multiple elements without wrapping them in some single container. Lustre should support this too! We should define a new
variant of the Element type...

  pub opaque type Element(msg) {
    ...
+   Fragment(List(Element(msg))
    ...
  }

... as well as exposing a new constructor for it.

pub fn fragment(children: List(Element(msg)) -> Element(msg) {
  Fragment(children)
}

Then the render methods to_string and to_string_builder as well as the runtime need to be updated to support this new element type.

Add API to dangerously insert HTML fragments without escaping

Hello!

I wish to insert pre-formatted HTML into my Lustre application.

It is rendered on the server, so I cannot do this with web component tricks, etc.

I could insert placeholder UUIDs and replace them, but this is awkward, error prone, and means I have to render to a string rather than a builder and then traverse and reallocate it once per replacement.

The ideal for me would to be able to insert the HTML string and have Lustre turn off escaping for this fragment. I'd prefer a horrible name to make it clear to the programmer that they should not be using unless they know what they are doing.

html.div([], [
  element.dangerously_insert_html_fragment_without_escaping(html_string),
])

Thanks!

No `effect.flat_map`?

Hi!

While there's an effect.map, there's no effect.flat_map, which could be useful to handle effect chaining instead of having to rely on the update chain?
We could need to cascade HTTP calls for example, and it would respect the usual Monad interface 🙂

I can understand it could be an "advanced" feature, but I think it could be nice to open possibilities for development, do you have any opinion on this?

Add support for elements keyed by their `id` attribute.

Most frameworks have the concept of a "keyed" node that lets the DOM patching do an in-place update of elements with a stable id even if their position in a list changes. This is important for two reasons:

  • as an optimisation to avoid destroying and recreating a lot of nodes unnecessarily
  • to ensure actual dom elements are preserved where expected for the purposes of animations etc

This was brought up by #79.

🔧 Allow for lustre_ui's default stylesheet to be injected by `lustre/try`.

I'm not entirely sure if this is a feature we definitely want, but for folks that are dipping their toes into lustre particularly from a backend perspective, they might be less inclined to put something pretty together off the bat.

Having the option to include lustre_ui's default styles (maybe with some additional styling that re-adds things like h1-6 font size etc) will let folks put together things that look semi-reasonable before they commit to lustre and lustre_ui.

This could probably do with some discussion or more thoughts on how/why we'd do this!

✨ Add logging to `lustre/try`.

It'd be really helpful if the lustre/try server provided some basic logging. At the very least it should log what host/port the server is running on. Bonus points if it uses ✨ emojis ✨.

Any other logs that might be useful are welcome too!

esbuild error when running example 01

Steps to reproduce:

cd examples/01-hello-world/
gleam run -m lustre build app

crashes with

Downloading packages
 Downloaded 20 packages in 0.03s
  Compiling argv
  Compiling gleam_stdlib
  Compiling filepath
  Compiling gleam_community_colour
  Compiling gleam_community_ansi
  Compiling gleam_erlang
  Compiling thoas
===> Analyzing applications...
===> Compiling thoas
  Compiling gleam_json
  Compiling gleam_otp
  Compiling gleam_package_interface
  Compiling glearray
  Compiling gleeunit
  Compiling snag
  Compiling glint

warning: Deprecated value used
    ┌─ /home/grfork/reps/gleam/lustre/examples/01-hello-world/build/packages/glint/src/glint.gleam:493:56
    │
493 │   run_and_handle(from: glint, for: args, with: function.constant(Nil))
    │                                                        ^^^^^^^^^ This value has been deprecated

It was deprecated with this message: Use a fn literal instead, it is easier
to understand
  Compiling justin
  Compiling simplifile
  Compiling repeatedly
  Compiling spinner
  Compiling tom
  Compiling lustre
  Compiling lustre_ui
  Compiling app
   Compiled in 9.67s
    Running lustre.main
⠋ Building your project
warning: Deprecated value used
    ┌─ /home/grfork/reps/gleam/lustre/examples/01-hello-world/build/packages/glint/src/glint.gleam:493:56
    │
493 │   run_and_handle(from: glint, for: args, with: function.constant(Nil))
    │                                                        ^^^^^^^^^ This value has been deprecated

It was deprecated with this message: Use a fn literal instead, it is easier
to understand
✅ Project compiled successfully
⠋ Checking if I can bundle your application
warning: Deprecated value used
    ┌─ /home/grfork/reps/gleam/lustre/examples/01-hello-world/build/packages/glint/src/glint.gleam:493:56
    │
493 │   run_and_handle(from: glint, for: args, with: function.constant(Nil))
    │                                                        ^^^^^^^^^ This value has been deprecated

It was deprecated with this message: Use a fn literal instead, it is easier
to understand
✅ Esbuild installed!
▲ [WARNING] Import "create_from_string" will always be undefined because there is no matching export in "build/dev/javascript/gleam_erlang/gleam/erlang/atom.mjs" [import-is-undefined]

    build/dev/javascript/gleam_erlang/gleam/erlang/process.mjs:78:18:
      78 │   let tag = $atom.create_from_string("EXIT");
         ╵                   ~~~~~~~~~~~~~~~~~~

▲ [WARNING] Import "create_from_string" will always be undefined because there is no matching export in "build/dev/javascript/gleam_erlang/gleam/erlang/atom.mjs" [import-is-undefined]

    build/dev/javascript/gleam_otp/gleam/otp/actor.mjs:135:10:
      135 │     $atom.create_from_string("gleam@otp@actor"),
          ╵           ~~~~~~~~~~~~~~~~~~

▲ [WARNING] Import "from_string" will always be undefined because there is no matching export in "build/dev/javascript/gleam_erlang/gleam/erlang/charlist.mjs" [import-is-undefined]

    build/dev/javascript/gleam_otp/gleam/otp/actor.mjs:169:18:
      169 │         $charlist.from_string("Actor discarding unexpected message: ~s"),
          ╵                   ~~~~~~~~~~~

✘ [ERROR] No matching export in "build/dev/javascript/lustre/lustre/cli/utils.mjs" for import "exec"

    build/dev/javascript/lustre/lustre/cli/project.mjs:18:9:
      18 │ import { exec, map, try$ } from "../../lustre/cli/utils.mjs";
         ╵          ~~~~

✘ [ERROR] No matching export in "build/dev/javascript/lustre/lustre/cli/utils.mjs" for import "exec"

    build/dev/javascript/lustre/lustre/cli/esbuild.mjs:22:9:
      22 │ import { exec, keep, replace } from "../../lustre/cli/utils.mjs";
         ╵          ~~~~

▲ [WARNING] Import "new_subject" will always be undefined because there is no matching export in "build/dev/javascript/gleam_erlang/gleam/erlang/process.mjs" [import-is-undefined]

    build/dev/javascript/gleam_otp/gleam/otp/actor.mjs:197:25:
      197 │   let subject = $process.new_subject();
          ╵                          ~~~~~~~~~~~

4 of 19 warnings and all 2 errors shown (disable the message limit with --log-limit=0)
❌ Bundling with esbuild

I ran into an error while trying to create a bundle with esbuild:

📝 Docs for the docs gods.

Doc comments are a bit lacking across the codebase. For a while Lustre's docs over at lustre.build were handwritten separate to this codebase but an open PR on the compiler makes it possible to export a package's documentation as JSON.

Being able to export the docs as JSON means we can use the actual codebase as the source of truth and folks consuming the docs on hex don't get a worse experience. Because we're pushing for another major version bump, now seems like a good time to polish this stuff up and make it good.

🔧 Add configuration flags to `lustre/try`.

Currently the lustre/try preview server is unable to be configured. Two configuration options we should absolutely support are:

  • a --port flag that allows the user to change the port the server on.
  • a --host flag to change the servers hostname, This will be useful for serving on 0.0.0.0.

I'm open to more configuration options too, but lustre/try is intended to be a simple preview server for folks that want to dip their feet into so it shouldn't be too complex.


We could consider using glint but it feels like it might be overkill, perhaps we can come up with our own abstraction that can be used in the cli too.

`gleam run -m lustre build app` errors out on the second invocation

The first time I run gleam run -m lustre build app, I get the expected output:

✅ Project compiled successfully
✅ Esbuild installed!
✅ Bundle produced at `./priv/static/taskmgr.mjs`

right after that the second time I run the same command, I get

✅ Project compiled successfully
❌ Checking if I can bundle your application

I couldn't find a public module called `taskmgr` in your project.

✨ Serve `main` functions that return Lustre elements with `lustre/try`.

The lustre/try preview server only expects full Lustre applications to be defined by an app's main function. For the folks super new to Lustre and frontend development in general, even that might be a little too high of a barrier.

We should also support the scenario where they return just a simple Element, such as:

pub fn main() {
  html.h1([], [
    element.text("Hello, world!")
  ])
}

So they can get something on the page ASAP.

✨ Add a `--spa` flag to dev server.

Most non-trivial lustre apps will be single-page applications that manage client-side routing. Its common for production backends serving SPAs to serve the app on all get requests so the app can load and take over routing, but currently lustre’s dev server doesnt do this.

This means of you navigate around an app that does some clientside routing and then refresh the page you end up with a 404.

We should add a --spa flag to lustre dev so that the app is always served on any route.

Unable to run examples: No module has been found with the name `gleam/package_interface`.

Hi, maybe it's a timing issue, as I'm using Gleam v1.0.0, and I see that main is in the process of being readied for a v4.0.0, but ...

I cloned this repo and then tried to run the first (and second) example, but get the following error:

error: Unknown module
   ┌─ /home/ian/Projects/github.com/lustre-labs/lustre/src/lustre/cli/project.gleam:10:1
   │
10 │ import gleam/package_interface.{type Type, Fn, Named, Tuple, Variable}
   │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Did you mean `dynamic`?

No module has been found with the name `gleam/package_interface`.

Here's a screenshot after I deleted my checkout, re-cloned, and tried to run again...

2024-03-10T17:26:01,478555591+00:00

Am I doing something wrong? (I'm brand new to Gleam, it looks awesome, and having used Elm in the past, Lustre could very well be my next favourite thing to use for web dev 😄)

♻️ Rework examples into something easier for people to read/learn from.

The examples folder in wisp is a great example of examples done right: examples are numbered and increase in complexity. You could read these examples in sequence and get a good idea of how wisp works.

Right now Lustre's example folder mostly serves as an ad-hoc test bed to make sure I haven't broken things in a while. Let's rework them into something other people can benefit from.

http server crashes when running ui demo with node

Error:

gleam run -m lustre/try --target javascript
   Compiled in 0.01s
    Running lustre/try.main
node:_http_outgoing:879
    throw new ERR_INVALID_ARG_TYPE(
          ^

TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Error
    at write_ (node:_http_outgoing:879:11)
    at ServerResponse.end (node:_http_outgoing:1030:5)
    at file:///home/wmoore/Source/github.com/lustre-labs/ui/demo/build/dev/javascript/lustre/http.ffi.mjs:82:15 {
  code: 'ERR_INVALID_ARG_TYPE'
}

Node.js v20.10.0

Steps to reproduce:

  1. Clone https://github.com/lustre-labs/ui
  2. cd ui/demo
  3. gleam run -m lustre/try --target javascript
  4. Open http://localhost:1234/
  5. Observe error in terminal

Environment:

OS: Arch Linux x86_64
Gleam: gleam 0.33.0
Lustre UI: 680b9ed

V4 Quickstart hard to follow

Hi, I just tried to follow https://hexdocs.pm/lustre/4.0.0-rc1/guide/01-quickstart.html and I did not look in the URL. So I followed the instructions and ended up with a Version 3 install. Took me as a gleam/lustre beginner some time to figure out what is wrong.

Then I took a look at some examples and in there I found:

Note: this guide is written for Lustre v4. The latest stable release of Lustre is v3. To follow along with this guide, you need to manually edit your gleam.toml and change the required version of lustre to "4.0.0-rc.2".

So I'd suggest to add some info in the quickstart to check for Version 4 to make it easier to follow

Create a `build` subcommand for bundling Lustre apps and components.

Folks using Lustre are unlikely to want to invest in learning JavaScript tooling just to build their apps, and while Gleam compiles to JavaScript just fine, its actual output is not bundled, tree-shaken, or minified, which makes it quite poor to serve on the frontend.

We have an add subcommand that can download a platform-appropriate esbuild binary (see the cli branch), it'd be great if we now put that to use and helped folks build their Lustre projects.

Building applications

$ gleam run -m lustre build app [ entry_module ]

If the entry module is not included, we should read the user's gleam.toml and assume the entry module is the name of the project.

As a technical detail, we will need to generate a tiny preliminary script that imports the module's main function and calls it. This will be our entry file to esbuild. That way folks will get a bundle that mounts and starts their app when including on the page.

Building components

$ gleam run -m lustre build component [ component_module, ... ]

Similar to building applications, we will need to generate a small script that imports the module's register function. In the case of bundling multiple components, we will need to import each register function separately (and make sure we rename them so they are distinct). It's important that we do not use JavaScript's * import syntax because we want esbuild to properly tree-shake the bundle.

Building multiple components

We should treat multiple arguments passed into this command each as a component to include in the same bundle. This way they share a reference to Lustre's runtime and we cut down on bundle size.

Accepting a wildcard

We should teat a single module with a trailing * as a wildcard that include import every module in that path as a component. This way someone could write gleam run -m lustre build component my_app/components/* and get a bundle of all their components.


Some misc tech thoughts

  • We only need to care about the Erlang target. We can execute external shell commands with os:cmd.
  • If the user hasn't already downloaded esbuild with gleam run -m lustre add esbuild we should fetch it for them instead of erroring.
  • The outfile should be somewhere in priv/. For building applications we could call the bundle the name used in gleam.toml. For individual components we could use the convention {app_name}-{component_name}. For multiple components we could name the file {app_name}-components. This could all be configurable via flags.
  • The format should always be esm. We're not living in 2016 anymore let's not encourage cjs.
  • In my own experiments I had success using stdin instead of generating an actual entry file.
  • We should support a --minify flag to minify the bundle.

Related docs

Remove the CLI from main package to publish it separately

Hi and first thanks for the package!

I tried to setup a Lustre application by using Vite to leverage on existing tooling (live reload, etc.).
Unfortunately, Vite refuses to build when internal node modules (like child_process) are required. Because of the imports chain, the CLI is imported in the Lustre package, and Vite refuses to build because of the presence of node modules.

Because of the nature of Lustre to be 100% browser compatible, I think it could gains to separate the CLI from the package, to avoid requiring node modules in browser.

Right now, to make it work, I had to do something like this:

// vite.config.js
import * as path from 'path'
import gleam from 'vite-gleam'

const childProcess = path.resolve(process.cwd(), './src/polyfills/child_process.js')
const fs = path.resolve(process.cwd(), './src/polyfills/fs.js')

export default {
  resolve: {
    alias: [
      { find: 'node:child_process', replacement: childProcess },
      { find: 'node:fs', replacement: fs },
    ],
  },
  plugins: [gleam()],
}
// src/polyfills/child_process.js
export const spawnSync = () => {}
// src/polyfills/fs.js
export const statSync = () => {}
export default {}

It's working, and the main function will never be used in the frontend, so it's not a big deal, but it would greatly improve as a quality-of-life to kickstart a project.

It could easily be separated by having a lustre-cli package, no? Unless it's not compatible with gleam -m lustre? In this case, isn't it possible to not bundle the CLI parts in browser?

✨ Add the ability to download tailwind through `lustre add`.

Tailwind is a popular library for quick styling. Many other frameworks and tools include official support for Tailwind like Phoenix or Rails, so devs coming to Lustre might expect the same.

We should expand lustre add to include support for the standalone Tailwind binary. Additionally we should detect if a project has a tailwind.config.js and if it does, download and run Tailwind automatically on build.


lustre add tailwind:

  • Grab the release binary for windows/linux/mac from here. There's no way to automatically grab the "latest" release, so the CLI will have to be fixed to a specific version.
    • We could consider a CLI flag for folks that want to get a different version.
  • Generate the following tailwind.config.js in the project root:
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{gleam,mjs}"],
  theme: {
    extend: {},
  },
  plugins: [],
}

lustre build:

  • If a tailwind.config.js is present in the project root, we should attempt to download tailwind if it is not already in build/.lustre/bin
  • Once a bundle is produced, we should then invoke the tailwind compiler to produce a companion CSS bundle.
  • We should not error if there is no platform-appropriate binary for tailwind, instead we should warn that we cannot produce the CSS.

Boolean attributes don't seem to apply

Using boolean attributes on elements doesn't appear to work as expected, I'm only using lustre to render html elements to strings, so I'm not sure if this affects the SPA side of things.

For example:

import gleam/io
import lustre/attribute
import lustre/element
import lustre/element/html

// executing the following I would expect "<input disabled>", but we get "<input>"
pub fn main() {
  html.input([attribute.disabled(True)])
  |> element.to_string
  |> io.println
}

I would assume that this is because boolean attributes are being registered as properties, instead of attributes, and so we are never hitting the boolean case here, but that's just a guess from a quick glance at the code.

Happy to take a look and make a PR

Lustre server component produces incorrect patches for new elements.

While putting together a collaborative whiteboard app, I noticed the client runtime was received updated patches for newly created nodes. This was causing runtime errors in the vdom code because updates are expected to have a previous node to diff against and there wasn't one!

Unable to set alt text to empty string

Setting the alt attribute to an empty string adds the attribute and removes it instantly. This causes constant modifications to the element (on every update), and the inability to actually set alt="" (which I think is a valid way to say "this image is not relevant to screen readers"). As a workaround, aria-hidden="true" can be used for images.

Screenshot 2024-02-27 at 16 06 46

Constant churn in DOM element:
https://github.com/lustre-labs/lustre/assets/273137/e65f684d-5203-44dc-b73d-7715401f0b62

♻️ Handle void elements and self-closing tags.

Right now void HTML elements like <br> and <img> are generated with closing tags when rendering to string or string builder. This is incorrect.

I'm about to push an ad-hoc fix that checks the tag against the list of known HTML void elements (it's finite) and renders them without the closing tag, but this doesn't scale because certain SVG and MathML elements are required to be self-closing like <circle />. We'll want a better way to deal with this than maintaining a list of hardcoded tags.

➕ Update JavaScript FFI to use React v18.

Running a lustre project these days spits out this error in the console:

Warning: ReactDOM.render is no longer supported in React 18.
Use createRoot instead. Until you switch to the new API, your
app will behave as if it's running React 17. Learn more:
https://reactjs.org/link/switch-to-createroot

It doesn't stop the app working but it'd be good to migrate over to the new way of doing things anyway.

Regression: select with option selected at start

When setting selected on an option in a select at first paint, the field is not the one selected.

Demo

Enregistrement.de.l.ecran.2024-04-07.a.19.17.34.mov

Gleam code as pseudo-code

h.select([event.on_input(on_cs_input), s.select_cs()], {
  use item <- list.map(list_)
  let selected = selected_item == item
  h.option([a.value(as_s), a.selected(selected)], as_s)
})

HTML produced

<select class="css-0006">
  <option value="Ayu Dark" selected="false">Ayu Dark</option>
  <option value="Ayu Light" selected="true">Ayu Light</option>
  <option value="Gleam" selected="false">Gleam</option>
</select>

And yet, it displays "Gleam" (the last one).

♻️ Add more context to existing `Error` type.

Lustre's Error type has a few error variants that would be much more helpful for folks if they provided some additional context:

  • BadComponentName should include the bad name
  • ComponentAlreadyRegistered should include the component name
  • ElementNotFound should include the query selector

We should wait for the Lustre Server Component branch to be merged in to main before we work on this. This will need to touch some of the FFI code in order to work properly (these errors are constructed in JavaScript)

⚗️ Come up with a way to actually write tests.

If we want people to take lustre seriously, we should probably have some way of testing the library to make sure things are working.

Probably the way forward for the runtime things is to use jsdom but we'd have to investigate their Custom Elements support if we want to have tests for lustre's components.

For other tests we could use gleeunit directly, for example when testing the output of element.to_string.

🐛 Multiple `class` attributes are combined at runtime but duplicated for static rendering.

As a convenience it is possible to supply more than one class attribute and have the runtime concatenate them.

html.aside(
  [
    attribute.style([#("align-self", "start")]),
    attribute.class("relative sticky top-0 hidden px-4 pb-10 h-screen"),
    attribute.class("lg:block lg:col-span-2"),
    attribute.class("xl:col-span-2"),

For things like tailwind in particular this is a quite nice QOL feature, but when statically rendering a lustre element to a string or string builder these classes render individually such that the above element is emitted as

<aside
  style="align-self: start"
  class="relative sticky top-0 hidden px-4 pb-10 h-screen"
  class="lg:block lg:col-span-2"
  class="xl:col-span-2"
>

This ultimately means only the first class attribute is considered by the browser.

Add children argument to html.option

To label option elements, we put text inside the option element like so:

<select>
  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
  <option value="3">Option 3</option>
</select>

But currently, html.option doesn't take in children as an argument. It only takes in attributes. So the equivalent gleam doesn't work:

html.select([], [
  html.option([attribute.value("1")], [element.text("Option 1")]),
  html.option([attribute.value("2")], [element.text("Option 2")]),
  html.option([attribute.value("3")], [element.text("Option 3")]),
])

🔧 Disable caching from `lustre/try` http server.

Because lustre/try is just a simple server for quick experimentation it makes sense for the server to send headers to disable clients caching responses. In the worst-case this doesn't really do anything but in some cases it will help prevent a lot of confusion/frustrating if old content gets served!

✨ Respect included `index.html` or `index.js` if available.

The CLI has commands for building applications or starting up a development server. Currently these only look at the project's main Gleam file but there are scenarios where a user might want custom startup logic in an index.js or want to customise the HTML shell with a custom index.html.

Our tooling should automatically respect these files if they exist.

tabindex attribute not being correctly removed from element.

Screenshot 2024-03-27 at 09 27 43

tabindex attribute is persisting on elements where it is no longer set.
In the attached screenshot the blue highlighed line has a "tabindex" value that is not set in the render function.
I am 100% sure that it's not set in the render function because the circled div is created in the same list.map call.

There is a tabindex value set in similarly nested div in a different component. which is the one highlighted below. So I think for some reason it is not being cleared when diffing properly

The code is here https://github.com/CrowdHailer/eyg-lang/blob/4ae1953e77e3b0fdab13c83eaf8ae997a2b59bd2/eyg/src/spotless/view/page.gleam#L21-L38

page.surface is the function that is used to render the very last element and sets a tabindex.
render.top is the function that renders an element without tabindex set.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.