Skip to content

feat: encode minimal data in url search params and the rest in the url hash to allow for longer urls#819

Merged
hatemhosny merged 29 commits into
live-codes:developfrom
BassemHalim:feat/hash-params-url
May 21, 2025
Merged

feat: encode minimal data in url search params and the rest in the url hash to allow for longer urls#819
hatemhosny merged 29 commits into
live-codes:developfrom
BassemHalim:feat/hash-params-url

Conversation

@BassemHalim
Copy link
Copy Markdown
Contributor

@BassemHalim BassemHalim commented May 10, 2025

What type of PR is this? (check all applicable)

  • ✨ Feature
  • 🐛 Bug Fix
  • 📝 Documentation Update
  • 🎨 Style
  • ♻️ Code Refactor
  • 🔥 Performance Improvements
  • ✅ Test
  • 🤖 Build
  • 🔁 CI
  • 📦 Chore (Release)
  • ⏩ Revert
  • 🌐 Internationalization / Translation

Description

Currently, while browsers don't have a limit on url length some servers do implement a limit (for cloudflare it's 32KB )
so very long documents would not load. I tested this with a 20KB url and the page kept loading indefinitely.

I understand that some information is needed on the server side for analytics and for social media sharing. This PR implements the following, the share url would become something like:
base_url/?x=code/{compressed({title: "", description:""})#x=code/{compressed(config)}

This helps maintain the same functionality on the server since we are using the same search params structure. However, I did not test the server analytics part yet.

This is currently a WIP. Feel free to close the PR if there is no need for this feature at the moment

This would allow sending minimal amount of data to the server while allowing for longer urls

Added tests?

  • 👍 yes
  • 🙅 no, because they aren't needed
  • 🙋 no, because I need help

Added to documentations?

  • 📓 docs (./docs)
  • 📕 storybook (./storybook)
  • 📜 README.md
  • 🙅 no documentation needed

[optional] Are there any post-deployment tasks we need to perform?

[optional] What gif best describes this PR or how it makes you feel?

…arams to allow for longer urls while still maintaining server analytics feature
@netlify
Copy link
Copy Markdown

netlify Bot commented May 10, 2025

Deploy Preview for livecodes ready!

Name Link
🔨 Latest commit 5b1c116
🔍 Latest deploy log https://app.netlify.com/projects/livecodes/deploys/682d13f5e6626100086dcdad
😎 Deploy Preview https://deploy-preview-819--livecodes.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@hatemhosny
Copy link
Copy Markdown
Collaborator

hatemhosny commented May 10, 2025

Thank you @BassemHalim

At first I was skeptical about this. However, after a quick check, it does work (see next comment)

The query parameters are part of the public API for LiveCodes. I would not want to change that without providing a backward-compatible way to guarantee that the older URLs would keep working (with the maintenance overhead for that). See the failing tests.

We do not need to change handling all query params. Only ?x=code/ needs to change. This is added in share function

Also the newly added ?params=... added by SDK

Let me think more about it.

@hatemhosny
Copy link
Copy Markdown
Collaborator

This does not work:

https://v45.livecodes.io/?x=code/N4IgLglmA2CmIC4QGED2A7AZhA5gVwCcBDSDAAgHkAjAK1gGMwQAaEAE1gGd6CIAHUukQgWIABawibYQB4AtrDBEy9MUQKdFAXgA6IAKoAVAGIBaABx6yAegB8O9PMXL0RBbpAA3CLADufVAIwK3oMMFh0MA9fCDYwMS0Ob3pYUxi4sWYyCHQoCCJoU24C2C0ARgA6AAYrO1ExMDloAEEwMA1haCJ0HA8IkK7OTg89USUcTkQAbQBdViJGCE9YAFE2KEDhOXUAazw+UW2CPYOEUC6evCIceCQjnbZUXyFWUMiIpiQAYjI0LFxCCQIORqHRGA4HBA5AEgmQADJLWBoDicMiYAioORkADkFQq1jx1k4BHo1lC0IwH041gRy2RXAqYE4AA9sQBuCHoWlI1Ao7Ko5YEACeaLgzIgVDgZG6bDIfAx3j5ynSsDIxB6qtQmBUGGw+GIgjIqAEwPQnAqDmaZAAUkRPEQAMo8fhgI20BiuiCovCaWWYQI6-76oHkGVkHawWB8HI4MicJThC3oBxfH76TjXWCcwwSQN6wGG1DuxgqbpkKiqn2wWW+MQQKU5PIFCAALxj0rI6D8cq6QpwGLw6FlPvb8VVUwdABEANIzAAUhM4bB21hyHGZFTkbGZAEokw4AAZHpmQimw4AqAiScIABV7-dQg9lAF80RisdjoIjQij2Zy50QnBCug9BkHOO5kFothkMADhkIG8Y9kQfYDkOkHSr4RBQJe16wHeyEPk+c7Yl8bxKDksAENiWSweg8HwW8eoIDBcH0fB1jWHmuBGiaGCohIV6sfBz7MKxz47hyyZ0TYnFjm6YKuvQZYVmqii8LAyzDpwo65go8S8mQB43GAfx6gerFvJwqBwBU0CoDgAGYdhfD3qhbAVMZpm4OBEmcuxnFhkpdEqV04QECoagalpOmqnpYgGQemgmbquDmdJRBOa6LkEW5FRJV5Dm0WxHGdt2jHccagicGJvnoOJ4GSUeaWWtAYWuJAyzQEKWRyfoABKcJkFMACOeCUSKfDqG4iiUfO+KjeNpiTcQcjmluu6GeVOAHqWdHyqgiqqso-WDWAqAdtaDoUAAcqp8pcB8IZ0VqZByVtBamvJHqvedKlVjWdYNrkkDNm2PSvbmRB8Hw+7oCszJuC5sAIJyTUOA0YB8JwCAcV+yw-gywLWAA-FtWgY1jOPWHIQqmPQPpnXIRSUYKFTkmSKU4BUNBWVJaNSamZCGEKfCwE6vACELItcNm0viy6r3S6i6jjo89MKO8soCbAc34lDECru8BCYAsXDWAVm5sBBgXKaqUIwuEfrvhDqpflQxDCrDTUnrkZ6umA0swb8nNkK+6KYjieMMLyXB-ug-Mpj8k6wCbeCtcHQacsnqfp1t-JDRw2BdlrlE63OFPYxxOBQGIeBUGzmLWFHdMx9SUcE9SkpFtYSSwHZfBEiSTffq3HNBj3KdEGnYAt0GjKcHunKCzeGIKRn4SRNmEiaNKV5yhiotBD4qLxCQZCFxRLuBhvrovW9hBXpEQ37QpuvWJg16EGbL8emt26LwLQWB5IAwFgM1dAwtRbMSmIldoMYDxv31obMKJsUjUgtluL4IC4A7izpPaezFzIGEiFAOAsoV6oAUnocBFC17YNgBUIWdZURejIP9aUqIphXk4NPOUmY34fxIF-ak3Dp6Ww2vQ6UaFJF6WUOMRhzRoBWTYb6bIdEpg-0YAIz+3DrCaKZOIiCmh1CqEYTmVhQVyyqiSmoq++gACSb4I5yUkTkPgeBXQASoAdVUoj06TRuBBAM4csS0O+hrPAZBABJhGQMJJZ7FYFQLDVMPwDwomdLxeOssoFDVgbwHoCCFx62jMgyiqCzYYLYF8dJEtBC4KktnKerVCF6GoZyOJroakulNIw9M1ZbEaNXh6bRQjdH6L-htYxJIxBSNlH4rKmZz5cAyYaWRr1rhmOYbtKxcZFC2N6o4kJsShklgidE45lDvoJP9MkoBEgpDgMgcjXJ8Z8nbUQSUnIKDTboM5pbL49yrZ4JzmAQh2InBKAiuoJKHgjBmEsCAGw9gHAQpcNNDw3g-AOxCGED40RYjxESBpCAKQ0gEsyGopshRihwHKNUWothsTgL+DfaUbAOCyjOlfLhXBeEBLLoSQRYBhHWHmYYwyMhAW2B2n3WAGswCbIsbbXZno6IHKcaEk5rozkxI6WQa5SSl53MaC0NoGhHnS2gXk9sAAfMgfVo4EDYDIV5MYsiup6NKj5BsvllJ+ebP5mCGhNBIO0BewKmmgsMp+bovQ9D9ERfQQYwxWkgCZZyVocCqAeK4GyjlP1uXzL4TcEZwrdFivWhBA8kqTXSrILK+VjD7GKWVWWIsa8AzKA9VzTk8NEZwEIReC4OBmLxuTCwFQybR3sF2FYZ8O0AwHhjT0Po47J2ARTTO44egmWiXQBWckqpq3BugGQYdq6Bgbo8GwWdIBpWw3MaiSxKkbE5DsYc52eqdUXLXga25qTxicAtTkmB3bZiFMJEg31xt-WVKwdccNDT8HNMMuB9pWr1kTF6aot9gzLlaKKe-HR38tUTIgtgVqlFZm7JMWIRVT7lWvrVbmBxGqf3hIiJE3VGG-1GtSQsDqqx1hnQIMB55MC9D3H2FYO1eh4xCjgDJsgcmVlKafCnCibAIPFJ9Ubcpvy56YIE4iasGwCD1IcI0gh8JAKunYaZkTaIAxVgIFkVAY4CAxB3pJ3Y0mQCcgdLKxgJ9IaLGWPW4TAYuWcHir4f9hlh1XBuEB7J4mDxwljUl2AswyB2vU0Xas2nrBQb07BwNVTEuZkQ5Z5DUaCinp4dDQIjsz2Zaq7YzAadT1Qz4NRjAXUVAPw+BFszrXLjtdw3KisbBOClpFVN6sZHYYIkQi9CIRBJT9Mq8lxhGXxvJYhmfFWnZ3PSmgFeKQIo7JSH6W+uSDnAiojnFMKTfB5xfFezuLIUx5NwHe792A1s0I-ZWf9lZO4ILPusUFlrRzlBsAPufJ4dEzln3u5F8K9DYYAEIpjIEijcGz+2BX4kXAmElq45A4CJDwSMZp4pMibm15LpgADM3M+A4As+gFJhlXtiatWsMzRWSvfLQQGwzVTPsRusweIdzPnl6BPXoLIZEPjTrneA5oz1iz+zUIpTmwir7baOmhNXT8766V871h7BB6PbJyEmvAHAr7+mgHZGI4N9qH0gFwFGUlTAJYVwef38EnnQL2-gfhhGkEB1FqYZsgEzaR6y5bep8ErMoaIcrkAO032vZGyJrIRD6BDGobYgHhfO1oSITQO0gFVO54rysqvdvWKPu2SpZQJvOzTSyLAZkG9tLkE7V+QCYFx8X2LgMnvas8DyqerNwjJvqRrgH4YgB8E5wMK5oZHzxxHjPGocXyTbBqHc8Dwec3YAQ-t8tS8uB4M8tDg08XEXnzSvi7g9f9PZBM9RqITaWkhzDtmBnyFPWvyNG1Dehjlb1hkv2v30AIGgFv2APv1A0f1jGfyn0K29VKRgy-3K1IlxUiEIGgF-3-0IXy00zSngitBOgLWu1lCvxIJvw1SbVdDUBPl+iOjIHtC-FlAYNPk9FRGQAoD6gdFMHW023cnbxYwGnzn6xFA-jAFUFu21BYPeEiB2m90ogDjIDUFlHQHOn4LGngMMjrHZQiBZQ+FQLD3QOtSfzYRfwKy0zwOg30wlz1H+UsI4BAlYIoNqyoJcJoNYgAAlYg-Dr5hthCyBjJUQNICgrgWsYh9IPErF2xvBtJNt9lcwCY4C5ClVgpKxNBOtT030FsrDZRsoUJHwhwnsd9GF-RwopB1hwYJBoBD40RBxFg+IsgPF6w8hc0osuAmQL8LDIjrDWCkCUDQ8pYQNHCsDnCcC3CY8P8xcKkiDfCIhr8yDAiQVgiVjaCyB6CFCuUmCJirD0AbDtD2D9VODx8uUu8+DmxBCzj9d84xCJCpDXAZCkww95DBpWElC0RFA1DZQIANDtjrjWCdCD49CRRDCTsXjoAzDORL83cOUEQuwgM5jw9clmgCBiAhQZALwQlmJ0B58KwCA2QfoKSqSqNnxoJsDX9cC1jdMNiDNvDMFMTqwvwcT9jI1DjWStNWJsTc1YjjInNoB81awIgr5bcz1UApBzQmFVR8iodnDeTZQqARQk0SUdh2xQwiSngrFtIXc5J+SGFCiGNiiVEU404nNwo4AhhVI4B7Qn58iKi5Bpt80ajCJ6jzDgFSEwE8SHDu1ctliRT38OS-VCDJcsEQzBTZdU1jj7FtQkoeotlWF2FHjcxJFLdVRFS7sWN7E3NBReA2jYw5JC5I1XoQyVUC1LSFde8FAwJGjlMQAbwhR9I11NT2Ech4xJBZQXo9BuywIAB1QCOQXBEAABBwS-Sw2AQwEMuw+YtLbxaySQOiFk1wmM-AzwuDJc+hZMrPVM1idMwydoMaA8LM1UAs6AvI2A4s-iSY9ALIWLDSKjKAJ9WA1heMesU9RI1Ekgasf4phIonZKsMo2xSov01yOombdsjyJogMSw9sQcLsNBDMYUHUDgec9AS-QIDgUTMMhYykn0qjXcmg9wz-TYhM4iyiU8gAqoY4kAo0J1KjQs1vXI1UBxIMzQOARgQINc-EjAt5SM6gt-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-pOU5jQ7vLu2FhP2W7OiAmI9wyA3pguE40BE-OGxCD68sBZ3kj+dj3uZWAHAdQNgF0lhAm3LrxpL8fQRsChcwyLX2AHjvj3343037oc3y3jAa3xhZObTrGyr0WZWVEbnpLmuC6-YFyBFnP-L6aWGOqpGBHgp3mLF1iPQEj9HpNzH5z87t2GftiS1kAMg-HzGMlwnxb190nkjmn3bvdZfzssPgyx5uPw-5fvQEvsvo7s-ggMaa5hn6SJ-p-jp3nYBUY0j+-A8HKrTSMo+9QCDGgAyA-qBAaVGLQALy5iczEbECRxWTUREaUabEFd0k4FQjcOdE3LPXCDxgJkINHvs1mrAXcFcC8ajJARehTBsBGtQjIXV0SUClsTNQUMPiySc1v+DqKQEoR2jiV4EODeMsDS6SZISGKGEto-FdBrtW4ZAAAGqUQmBD6bMsrFUhSBTAIJXDiKFrAkoZk6jBgPC2b6DZS2ogxEOuz4LSCek9xPnnGFix0QRw4MOSFOGnC5IB8DAIrEuBXA8xyeuAhwfQAghxReQXsMfg4BKhvp3YqjKjJZC3LnUyi3WaGMhV3xRt24rcCoMCHqQZQsIroOISiAqAeDiIMWJ4FII0CmhsQtUDprkKYFxg4W2AQPgrWZDts2As9W7vZWNrjIzs8nA3tsEgDvsBs+wfsFIBigKc14h1Ubl7ysrVDywIoMsD1k77xAOwXYXwFRkYE9IjUJxKIRg0QZbwHo87CXljTPg4EDCppOSOMIrBqBlg5oJmhdkeDoAuoh9KvvHB4H80uYmCU4UoUEEAETYSiMBJyEvLAJ7+8fVvK71dgGUKhpwpQecJFCBNeuyXJ9iUW-Kogm2TQsFpynOiqBY0EIvCtaSkj28lkIKLIIMIPAPDgRMLRsrHy+G3ljcuABoGkFgCkiDcLuRUnjhji2gCAtQjHOwzQ5gMY4UhDHBBBzK+gQ+AHTvjFm3o7xug7mASK3ndR7Ic6tud7Lbhw7wk4On1erE8Ejz44kRlw1jsJ2uHn1vaclO4VUgVG+Bh0iIjUE8MIRx9wEHwl4ZoGJHqoYg7uE7K6GyG+Ar4ZyWInqKfQE52wipE3J9UxS+BD6cmXvpc1rogBbcbdEAPMmoQ3CfaOor4D6ONGj11GX4S5jK1Vi1Y+CPgXwLPUd6tRTAPorhlFlzD+lcoTNUEX6JAARDQx-oahqGO0g9dQxiYrMIilkxhj7agYpsSGMbGdkCYXcegDsAjGajZGRBUEXGKITljx6UkeBoNymD+saiuXEnERn8bUhpxvYZxvEJi5M0zo1kL-gsRvCADwCpJBdhtjITMRCSxJGQNiFCFwArudqc8ZiGjBkIrxOIOgdiGZI4h6s7IaUGFmeQ3izQW5B8TeOhDjc2Af4p8ZGWQG0l4wQibGDiCTRFVqwf4yPugD-ERC-xxhLsH+PfFMkZGgNKMf8g3FKJhxF4aQkeNfHu4EBxmZYDAIQEQThUUE5ASjQCzs0DxMhajORPvJqit2soaiT6HOoUD2JfCLsFgPYlLRug0XEur2gRg9dR+6LcfugBR54SzW13bZsGJ-rVhHmUwPQBeIbFZANJt4wCXoDmAY8QArEm1ppJDaz8QAXEhSXoAQl09Qer-CHiijEEogLInMLQMAHxbTNNmi1LLDAJhoN5akCAjap2L4hblbI9kYiGET7h2QyAE5QIDKWxwFCOQW1C-vBFYmgCYB0zaiNc3kleSiJ1YaBN+NCawAEB-4u8XBLmAfjBMMAzSVRITA+gYBCElARf2fBP9bAMgGkAYNbhLNE4qSFsKxzkCH0ygkZKoBUDarDTuWbVLCXzRwmYI+pmIYcWUHAQTjhmedQnFMDmlYh3SfcWesXWZBfB5kwkm4KYA2kEVBYfScKMsKwarDBRe8DYSFi2GsluULmFVGhzmgEgC6ttawC5mZGINDEWQR3KiSrIK1-OWHCYIyLMw-TOGa4wBKkjynNB7EqotjhqMgzrFsJ2o-5HlKwjDiLRbwyHhoVNHfZ4ZKIjhNpAgmRBZ6WEcVKwjymyEYZofF9vsBvThBEZ6oqaX3QTIG9GZoFYcaaPeH4yiRzvA9oslYQtC329WEUFzJazkANSBOXrkQEwBhR6ybZKcX3GQjvZnWyEU6UAgN4ZhlgLMs3pGPRlGYX2uswHBAMIQ4yzR-Mm8s730TxcX2rQklOLLjB2hf2QfWAoaJuCpMFZVGSAMrI1lCh1ZqsoUFrP4wvtKB+s6vobLgwG9KB2MgoJaL5lXkBZV8O2awkwiqEJAfoAMDLKRGog1BaXOBDgBuCY5P+JtF9inUHCfUA5h9CitSTZm4NoxAchaW1SqCsVswUIJrr2DAj55AKwXN4DNmpoqNPcsYXORqG9mKyC5wvfdi2OLR+82EfAJmf0jnCQsYEnMxeaBUKRfB15S8zkaiE+E3kgcLDfMaqDTlqNXZsoFedqDXkmzXZW8nWa7L3nJzD5n1Zoq0IoBmg75VdXJFcIbm8C8Gb8kgHxEfnmzDIlspOQfO+EwEXcIsh2WLI-ZDRAFPnGodQM+kEw3KgQVoeKnIC6yeh-DJmr2EfCV0WBCxJXIEFbBhACgegJsYKDaFUKOx7dLgAEDNCIgrAklEIklRRmxlppRsirMhCIXDjGF6jPiKwrHEOBlpewqIYQvSJcp4ovAPqZEGVoBhaFTs6ACHxj7nQiE3CZhdpGWDl45wNZWrEEnCg-8OFhWZ3tIpEJGCj477CvI3Qay04QuSXSdqnIQpPgDCFInAA0HziucS5h2bdlN3SDxA3MHmLzKqDkUULFFai9cWB1xlu0HCegL8F4tbGdkb026Huv2LRlwYxwCgQRVul7FiL0AEiyGFEIoGxKsBsSsjIwUpE9xdgs4z6jkp5B2RRM38rgU4V-5-zbhuE2JaEGaV5KxAnAaAHOA5ZlB+WWQfllUAACkWQVuZMtnJa4aO7YLLmVyIYdhJ62lXpcoqSJlwo2vcfuJRE3CoA2w7uIgOZwIDU4IgpgfQA6B7iQNqQE5WAFQHNhaUOYzSgAPrFcvs9peEQSJKW9YylcqKqs0oqWAqlspg2rugBFD8FYgvwKepsvCjFd3UeANLuPiIRfB+Wk4W3qzh5YrBj8o9c5VQGGX8sOqZADlhyxJX8seW8yk-OIEGVzhWcHLMZWQAmXTKyAsy+ZdRiIRJKGgkoB-rnk+q24QCCgQ+qAKFXZYZgkZCMolTZJcKUq-86MbbkaXDiOlDEqbtAqLL+dGlxwqSAR1zbnQC24QBwAAE1T2lie6D6Oow2JFSWqoZn51Gygy1Gji9APOEmjxACuyMDiO9JJig06cWgW3JDOIZJgCOW8J7kNHyK250O0o+dm2wIEQk6ITkv3GQAABUSavjq4FCApqwIhikFEaDogogdgZ0PgF8pTX0hzKyvDEAQEzUGLcwWY2+CjiLDjcj5yapNfSHpFVrs1dZXDF2Lsg9jfGH0hcUKwTHIQVx6Q1et2sgY7AvluGesbONnr1jxUYYXDLiK6jvZl1IcpCLUSfDQMyAKwBYDMkVJcFsgU3ZHI2ULIKBOAWQZZQ9E1gjCr4JHK1GmoWCoAxVU0y+ut1vpmxH1oQMVWnm+wHh6QcgctYEBfW8031TnD9b8jO5AaCAP6l+tRhgT0g68MG2Ja+oKCOd48EGgNBwCQ2wbVeqwwyIKpQ0KcfcepZVOwmizijcw1q2JY9VS77r-OYYcgNyoZZpKdgs41UlD0YziiQOdEdQMSR4kHhRVKG0DWhvfVj4761gITYCrTxHq5UqITtIsvBgvRyQ2wZmMtFAoQlwgq0Ifnutk1YhNS5IHyr+0wAI9W8CAHaMAliWmB3VFmtlj1nJ4obx22sKXgRoxz4i5I7q86ujlGxzgiEz1dNagFDEExANJpAgEFpjhIbz8jCKYBQEyQFAZgRfSzYCus3TR3NuYTzdxUaXRDGEfmjAKgB2BYQotQ0B1ErxV4JbpIEg14i7DbLurbSOyf0O4uc2sRA8X61AA+rr7frhNmSlntAHQ031xNn6jrc+tiVp5mtwcDgGWtC1WoAN0GkDd1rA0YaBtkGuVLNpG0v0xtra9QNNoi3qA5tsqi+qJvA1LasNsAHDWtrw2EVQ+0MBzYCrS0882y3FcYVlt81X9b0QY5jUVpi1xboA5WhwMPxdIuaiEngNamlEvw7MAtCAMADaMpGdUoNU2ykB5VvSg7UMegYHWyw+0gAP6l24Oqjs4Do7KRCNXHYjuOBstWN+k5HTjpAD+an1CANHRju0kgBgt0GhAGY1rhUBceyS-HcksJ3sBiA9MLoKTtvRY6-tumwTRjhfV6bVIpW3NHIho3uq1e1W+7eqTbTfaBsKke6Mw095d9vNjmTzZoHU0tZdSHYTZWqnOiaDNGmAEUDroDDb4UKo9anaEAQD+b8thW3PDb0428alE50WvrrTV2qhReMwnUlbr+WK7dkBu-pEbr0BssrAtu3fLluMIFaIAgu9JQeAIrmUq2SMQyFJuFV6a6tL6Hvl+GXnj4+NyEXPXe0qo6g5Aqm-XS136TdoII0WMocHohFtl5adGniourzV1KwwzG9jQrsYrhRWhYUVUuxWwAaBXQ0KzTXKiGYl74O4UCMtwzyJDYn4ipXzQ7tQDEiWCcOitQuhMUEwkNqeliVEOe0HhmNu+4vrsEP02jT0f0bkfqm1DGEy9rQwuUvr0Ed60IT22Jc71rWh7F9Gq0bGGE-2ArO+to2-YXzpkHgesKeTMIfWaDQxoDhOaVasX21aiY50ME3MqrMWilxxAQCMHRB7z5FYiSUQbuMNYw95Y9OWvQOoDxVEIWwYgUwMgGuhFa+kca16OqEGWL5HqB2wQKDCegh9TFRxLNTWqMUWKWysCxmPAoGxvUPQEepPCOW972kqIqIIIb4E0Dz7UOiDajC8KUTlgFgbGrlCsB6BfgYs-07UNId6I68Ga8mzHNjTEPQjTsMaoIOAZ6mGQrwoQQUPoE-maRI5yMnTHKq6X3Do4goQcKbKBRIYR6kC8BIYcPG5oeU7hyiO2EGB2YvD-SfRDtKCMHLjagwyRb1lYRXgEJzhojqkhmHQBD0PhzpTNKqQlHD0PMokRAoJncpqjpTOOk92dWoKB1TRhQFTNRBLjkInvFxTkaVIqknasWB0JNE0YFJWlJvNUQbO63szgaDo9RgsBjDxzXhURlSZwgdEei4A8qUocsZ6DpGRmDlB0UUHGOINORChoWTcAFVuapj4Op9eFu32BBHjp26gwwqMkvs1MWBio7wq+DSjQFAh6MgxN0qTjw1GOcGSJgDW4D0FUarlDvQgUdKhDTXEFIDxkDuwkUDgVrTxS5FuylknAAtcaGYBonwodgBwKWug35x2E5AZ6lQHG7UY30iUe2HAB2igiiT6J0k500BiqhNt4UHE6wZgTjqexLJmOJmK9A1ER1RtbcMQQ4DdjJ1-09RKfqgBgJZ17RstGbHnXG1O9uSNdVvLXU4c3FgZJORovplnRiRWJl8t8tzV4mCTvWMMOSdC2UnVE1JhtVKCvBp8nUmfc6sKKowOrX5YQYwG4HrBChD6UqqMnuWjlEF-QkQE2IBq6iYGjiqq0E9VTMwQnAgUJvMU-BjNBm-TkQB0K2DiXINyKIVdhUcQjMJkozYALM3GYBMqrxxjEnOmCZTOqn5sTI308bQrNxh8zsXDQoierXInI0XykXmEE7PN1o+3GsgKMseoi8us0oKIWGDKActI6lRPtfOLVOJ1KKM2R2kzSrCGANsW4jcjMajlzHG5-yKsEoCoAKNpO4CvGc-O+FWlfhaiPwikW117m1EQ5KQOdSWPp0TiXuy012tgJIKiGIDNkUgq3MQHzzeZ5urXJCo-Hsle5-M8OI5bJjOwxZl6F+dzRdE18iiwQBdy-JqKfzyiAcuonyKAWYwwF1yqBehkuHFTXYFqtSX3NWorhQYq8KFFEWwWiCVpOudINqM3kAssWM9JfC4saABkjZkTKmYIDpnoZl5Y01otVmCY8VVpFC+FQfMOi6ILF1nmxKvhlURBAlrsPbmh4OlT0QvIaN4CxCgipRGOIcZ9S6ZOoJyxAPgOUbLPA0bLbAXwPZdWOJypI0RzbPnOIppB7Lj1OyODHvOl07ImgW3nzojBCMpjv8py3gxgmaB3Yeh9mjxYLN9JpQFcwzXAHCAFoErqoJKz2MYlhhRohbbVdRcxIOpq4fERy8eflX-JMSbp3Th5YLMTt5SMlho41b4iGNjgEekUAeH2mp9TQO0MMP1YiAI5BrGAHaCppy7HZtSzi+UnJDPlDGOUAqqvYoBqsoGBxCZOVHpFSvrGYjnCFYGtbADiXJLUpna4oFDmuazM5fAs1zVR1QhQxcqBYGtRLPRk4rCqyyzHHjNAm6zU3O1Y5nMvNndE-qts+daZGgjoGTNTQIBpN0MWf5h53w8VlRk8K4MMNklIbzNB7XOQaAo6uXQQa+cSLmC0NLNGBtmx0FlF-+AKPHDo2W4QVvThvypj3QCbduImFZznorcfd5oE9CDRW1w2rrjJow7AAACKeAQthtb8OTU6rgtGMHABKvhBmrzKQ3HjavAs2w1AF4mz5wZFk3qQFNzWxnXOhVgho2kIW2QHlu5pBy4QD8y9EeB1wpQ5thmy8z0Sq3fcrN1ALctLwc3rAXNjziai+AO2Bb7QBmjGDQBV6iAEt5G9wvmN4Mg7gxHoCpqIDY3i2yt3NPjddvq2XcgF0m34zXMcwKL+t42nCZ3gUDiAcd0eZiG2CO2yWzN12wkPdvs2Ig3tzJNzb9ux3+SOAFuGHegYgARIFk1WrcHOAK5hAq1UQNfmEBssDGegVnXXGca7BD8hFVuiAEkgOBvEbAEUCs28ScVQqE5vgMyDjDWQYVfxsoLAH5awBzAIPTeyRRTrdD6pzK3eyD0mjsoYwzEDllUHvusQjg1cHqqSrft73scgjboGAEkhP8e7rAaZogEHvE5hAfk8B68FYLCA58jaVe0KDZjJopaFQVosRHuDz3TAyDxKQ4FAd92WawtdmtMDmAWT-aZD14EMHLpJRhAogQBkMEexUPGdaDS6RMAge935GnD1gCRx4fgBP+ED25kPaQBwDako9+B0gEIdzCMAwgYlWMHYlCOaZwgerKIFYnD2QpcAUQJZOHthXqwPd58EAA

while this works:

https://deploy-preview-819--livecodes.netlify.app/#x=code/N4IgLglmA2CmIC4QGED2A7AZhA5gVwCcBDSDAAgHkAjAK1gGMwQAaEAE1gGd6CIAHUukQgWIABawibYQB4AtrDBEy9MUQKdFAXgA6IAKoAVAGIBaABx6yAegB8O9PMXL0RBbpAA3CLADufVAIwK3oMMFh0MA9fCDYwMS0Ob3pYUxi4sWYyCHQoCCJoU24C2C0ARgA6AAYrO1ExMDloAEEwMA1haCJ0HA8IkK7OTg89USUcTkQAbQBdViJGCE9YAFE2KEDhOXUAazw+UW2CPYOEUC6evCIceCQjnbZUXyFWUMiIpiQAYjI0LFxCCQIORqHRGA4HBA5AEgmQADJLWBoDicMiYAioORkADkFQq1jx1k4BHo1lC0IwH041gRy2RXAqYE4AA9sQBuCHoWlI1Ao7Ko5YEACeaLgzIgVDgZG6bDIfAx3j5ynSsDIxB6qtQmBUGGw+GIgjIqAEwPQnAqDmaZAAUkRPEQAMo8fhgI20BiuiCovCaWWYQI6-76oHkGVkHawWB8HI4MicJThC3oBxfH76TjXWCcwwSQN6wGG1DuxgqbpkKiqn2wWW+MQQKU5PIFCAALxj0rI6D8cq6QpwGLw6FlPvb8VVUwdABEANIzAAUhM4bB21hyHGZFTkbGZAEokw4AAZHpmQimw4AqAiScIABV7-dQg9lAF80RisdjoIjQij2Zy50QnBCug9BkHOO5kFothkMADhkIG8Y9kQfYDkOkHSr4RBQJe16wHeyEPk+c7Yl8bxKDksAENiWSweg8HwW8eoIDBcH0fB1jWHmuBGiaGCohIV6sfBz7MKxz47hyyZ0TYnFjm6YKuvQZYVmqii8LAyzDpwo65go8S8mQB43GAfx6gerFvJwqBwBU0CoDgAGYdhfD3qhbAVMZpm4OBEmcuxnFhkpdEqV04QECoagalpOmqnpYgGQemgmbquDmdJRBOa6LkEW5FRJV5Dm0WxHGdt2jHccagicGJvnoOJ4GSUeaWWtAYWuJAyzQEKWRyfoABKcJkFMACOeCUSKfDqG4iiUfO+KjeNpiTcQcjmluu6GeVOAHqWdHyqgiqqso-WDWAqAdtaDoUAAcqp8pcB8IZ0VqZByVtBamvJHqvedKlVjWdYNrkkDNm2PSvbmRB8Hw+7oCszJuC5sAIJyTUOA0YB8JwCAcV+yw-gywLWAA-FtWgY1jOPWHIQqmPQPpnXIRSUYKFTkmSKU4BUNBWVJaNSamZCGEKfCwE6vACELItcNm0viy6r3S6i6jjo89MKO8soCbAc34lDECru8BCYAsXDWAVm5sBBgXKaqUIwuEfrvhDqpflQxDCrDTUnrkZ6umA0swb8nNkK+6KYjieMMLyXB-ug-Mpj8k6wCbeCtcHQacsnqfp1t-JDRw2BdlrlE63OFPYxxOBQGIeBUGzmLWFHdMx9SUcE9SkpFtYSSwHZfBEiSTffq3HNBj3KdEGnYAt0GjKcHunKCzeGIKRn4SRNmEiaNKV5yhiotBD4qLxCQZCFxRLuBhvrovW9hBXpEQ37QpuvWJg16EGbL8emt26LwLQWB5IAwFgM1dAwtRbMSmIldoMYDxv31obMKJsUjUgtluL4IC4A7izpPaezFzIGEiFAOAsoV6oAUnocBFC17YNgBUIWdZURejIP9aUqIphXk4NPOUmY34fxIF-ak3Dp6Ww2vQ6UaFJF6WUOMRhzRoBWTYb6bIdEpg-0YAIz+3DrCaKZOIiCmh1CqEYTmVhQVyyqiSmoq++gACSb4I5yUkTkPgeBXQASoAdVUoj06TRuBBAM4csS0O+hrPAZBABJhGQMJJZ7FYFQLDVMPwDwomdLxeOssoFDVgbwHoCCFx62jMgyiqCzYYLYF8dJEtBC4KktnKerVCF6GoZyOJroakulNIw9M1ZbEaNXh6bRQjdH6L-htYxJIxBSNlH4rKmZz5cAyYaWRr1rhmOYbtKxcZFC2N6o4kJsShklgidE45lDvoJP9MkoBEgpDgMgcjXJ8Z8nbUQSUnIKDTboM5pbL49yrZ4JzmAQh2InBKAiuoJKHgjBmEsCAGw9gHAQpcNNDw3g-AOxCGED40RYjxESBpCAKQ0gEsyGopshRihwHKNUWothsTgL+DfaUbAOCyjOlfLhXBeEBLLoSQRYBhHWHmYYwyMhAW2B2n3WAGswCbIsbbXZno6IHKcaEk5rozkxI6WQa5SSl53MaC0NoGhHnS2gXk9sAAfMgfVo4EDYDIV5MYsiup6NKj5BsvllJ+ebP5mCGhNBIO0BewKmmgsMp+bovQ9D9ERfQQYwxWkgCZZyVocCqAeK4GyjlP1uXzL4TcEZwrdFivWhBA8kqTXSrILK+VjD7GKWVWWIsa8AzKA9VzTk8NEZwEIReC4OBmLxuTCwFQybR3sF2FYZ8O0AwHhjT0Po47J2ARTTO44egmWiXQBWckqpq3BugGQYdq6Bgbo8GwWdIBpWw3MaiSxKkbE5DsYc52eqdUXLXga25qTxicAtTkmB3bZiFMJEg31xt-WVKwdccNDT8HNMMuB9pWr1kTF6aot9gzLlaKKe-HR38tUTIgtgVqlFZm7JMWIRVT7lWvrVbmBxGqf3hIiJE3VGG-1GtSQsDqqx1hnQIMB55MC9D3H2FYO1eh4xCjgDJsgcmVlKafCnCibAIPFJ9Ubcpvy56YIE4iasGwCD1IcI0gh8JAKunYaZkTaIAxVgIFkVAY4CAxB3pJ3Y0mQCcgdLKxgJ9IaLGWPW4TAYuWcHir4f9hlh1XBuEB7J4mDxwljUl2AswyB2vU0Xas2nrBQb07BwNVTEuZkQ5Z5DUaCinp4dDQIjsz2Zaq7YzAadT1Qz4NRjAXUVAPw+BFszrXLjtdw3KisbBOClpFVN6sZHYYIkQi9CIRBJT9Mq8lxhGXxvJYhmfFWnZ3PSmgFeKQIo7JSH6W+uSDnAiojnFMKTfB5xfFezuLIUx5NwHe792A1s0I-ZWf9lZO4ILPusUFlrRzlBsAPufJ4dEzln3u5F8K9DYYAEIpjIEijcGz+2BX4kXAmElq45A4CJDwSMZp4pMibm15LpgADM3M+A4As+gFJhlXtiatWsMzRWSvfLQQGwzVTPsRusweIdzPnl6BPXoLIZEPjTrneA5oz1iz+zUIpTmwir7baOmhNXT8766V871h7BB6PbJyEmvAHAr7+mgHZGI4N9qH0gFwFGUlTAJYVwef38EnnQL2-gfhhGkEB1FqYZsgEzaR6y5bep8ErMoaIcrkAO032vZGyJrIRD6BDGobYgHhfO1oSITQO0gFVO54rysqvdvWKPu2SpZQJvOzTSyLAZkG9tLkE7V+QCYFx8X2LgMnvas8DyqerNwjJvqRrgH4YgB8E5wMK5oZHzxxHjPGocXyTbBqHc8Dwec3YAQ-t8tS8uB4M8tDg08XEXnzSvi7g9f9PZBM9RqITaWkhzDtmBnyFPWvyNG1Dehjlb1hkv2v30AIGgFv2APv1A0f1jGfyn0K29VKRgy-3K1IlxUiEIGgF-3-0IXy00zSngitBOgLWu1lCvxIJvw1SbVdDUBPl+iOjIHtC-FlAYNPk9FRGQAoD6gdFMHW023cnbxYwGnzn6xFA-jAFUFu21BYPeEiB2m90ogDjIDUFlHQHOn4LGngMMjrHZQiBZQ+FQLD3QOtSfzYRfwKy0zwOg30wlz1H+UsI4BAlYIoNqyoJcJoNYgAAlYg-Dr5hthCyBjJUQNICgrgWsYh9IPErF2xvBtJNt9lcwCY4C5ClVgpKxNBOtT030FsrDZRsoUJHwhwnsd9GF-RwopB1hwYJBoBD40RBxFg+IsgPF6w8hc0osuAmQL8LDIjrDWCkCUDQ8pYQNHCsDnCcC3CY8P8xcKkiDfCIhr8yDAiQVgiVjaCyB6CFCuUmCJirD0AbDtD2D9VODx8uUu8+DmxBCzj9d84xCJCpDXAZCkww95DBpWElC0RFA1DZQIANDtjrjWCdCD49CRRDCTsXjoAzDORL83cOUEQuwgM5jw9clmgCBiAhQZALwQlmJ0B58KwCA2QfoKSqSqNnxoJsDX9cC1jdMNiDNvDMFMTqwvwcT9jI1DjWStNWJsTc1YjjInNoB81awIgr5bcz1UApBzQmFVR8iodnDeTZQqARQk0SUdh2xQwiSngrFtIXc5J+SGFCiGNiiVEU404nNwo4AhhVI4B7Qn58iKi5Bpt80ajCJ6jzDgFSEwE8SHDu1ctliRT38OS-VCDJcsEQzBTZdU1jj7FtQkoeotlWF2FHjcxJFLdVRFS7sWN7E3NBReA2jYw5JC5I1XoQyVUC1LSFde8FAwJGjlMQAbwhR9I11NT2Ech4xJBZQXo9BuywIAB1QCOQXBEAABBwS-Sw2AQwEMuw+YtLbxaySQOiFk1wmM-AzwuDJc+hZMrPVM1idMwydoMaA8LM1UAs6AvI2A4s-iSY9ALIWLDSKjKAJ9WA1heMesU9RI1Ekgasf4phIonZKsMo2xSov01yOombdsjyJogMSw9sQcLsNBDMYUHUDgec9AS-QIDgUTMMhYykn0qjXcmg9wz-TYhM4iyiU8gAqoY4kAo0J1KjQs1vXI1UBxIMzQOARgQINc-EjAt5SM6gt-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-pOU5jQ7vLu2FhP2W7OiAmI9wyA3pguE40BE-OGxCD68sBZ3kj+dj3uZWAHAdQNgF0lhAm3LrxpL8fQRsChcwyLX2AHjvj3343037oc3y3jAa3xhZObTrGyr0WZWVEbnpLmuC6-YFyBFnP-L6aWGOqpGBHgp3mLF1iPQEj9HpNzH5z87t2GftiS1kAMg-HzGMlwnxb190nkjmn3bvdZfzssPgyx5uPw-5fvQEvsvo7s-ggMaa5hn6SJ-p-jp3nYBUY0j+-A8HKrTSMo+9QCDGgAyA-qBAaVGLQALy5iczEbECRxWTUREaUabEFd0k4FQjcOdE3LPXCDxgJkINHvs1mrAXcFcC8ajJARehTBsBGtQjIXV0SUClsTNQUMPiySc1v+DqKQEoR2jiV4EODeMsDS6SZISGKGEto-FdBrtW4ZAAAGqUQmBD6bMsrFUhSBTAIJXDiKFrAkoZk6jBgPC2b6DZS2ogxEOuz4LSCek9xPnnGFix0QRw4MOSFOGnC5IB8DAIrEuBXA8xyeuAhwfQAghxReQXsMfg4BKhvp3YqjKjJZC3LnUyi3WaGMhV3xRt24rcCoMCHqQZQsIroOISiAqAeDiIMWJ4FII0CmhsQtUDprkKYFxg4W2AQPgrWZDts2As9W7vZWNrjIzs8nA3tsEgDvsBs+wfsFIBigKc14h1Ubl7ysrVDywIoMsD1k77xAOwXYXwFRkYE9IjUJxKIRg0QZbwHo87CXljTPg4EDCppOSOMIrBqBlg5oJmhdkeDoAuoh9KvvHB4H80uYmCU4UoUEEAETYSiMBJyEvLAJ7+8fVvK71dgGUKhpwpQecJFCBNeuyXJ9iUW-Kogm2TQsFpynOiqBY0EIvCtaSkj28lkIKLIIMIPAPDgRMLRsrHy+G3ljcuABoGkFgCkiDcLuRUnjhji2gCAtQjHOwzQ5gMY4UhDHBBBzK+gQ+AHTvjFm3o7xug7mASK3ndR7Ic6tud7Lbhw7wk4On1erE8Ejz44kRlw1jsJ2uHn1vaclO4VUgVG+Bh0iIjUE8MIRx9wEHwl4ZoGJHqoYg7uE7K6GyG+Ar4ZyWInqKfQE52wipE3J9UxS+BD6cmXvpc1rogBbcbdEAPMmoQ3CfaOor4D6ONGj11GX4S5jK1Vi1Y+CPgXwLPUd6tRTAPorhlFlzD+lcoTNUEX6JAARDQx-oahqGO0g9dQxiYrMIilkxhj7agYpsSGMbGdkCYXcegDsAjGajZGRBUEXGKITljx6UkeBoNymD+saiuXEnERn8bUhpxvYZxvEJi5M0zo1kL-gsRvCADwCpJBdhtjITMRCSxJGQNiFCFwArudqc8ZiGjBkIrxOIOgdiGZI4h6s7IaUGFmeQ3izQW5B8TeOhDjc2Af4p8ZGWQG0l4wQibGDiCTRFVqwf4yPugD-ERC-xxhLsH+PfFMkZGgNKMf8g3FKJhxF4aQkeNfHu4EBxmZYDAIQEQThUUE5ASjQCzs0DxMhajORPvJqit2soaiT6HOoUD2JfCLsFgPYlLRug0XEur2gRg9dR+6LcfugBR54SzW13bZsGJ-rVhHmUwPQBeIbFZANJt4wCXoDmAY8QArEm1ppJDaz8QAXEhSXoAQl09Qer-CHiijEEogLInMLQMAHxbTNNmi1LLDAJhoN5akCAjap2L4hblbI9kYiGET7h2QyAE5QIDKWxwFCOQW1C-vBFYmgCYB0zaiNc3kleSiJ1YaBN+NCawAEB-4u8XBLmAfjBMMAzSVRITA+gYBCElARf2fBP9bAMgGkAYNbhLNE4qSFsKxzkCH0ygkZKoBUDarDTuWbVLCXzRwmYI+pmIYcWUHAQTjhmedQnFMDmlYh3SfcWesXWZBfB5kwkm4KYA2kEVBYfScKMsKwarDBRe8DYSFi2GsluULmFVGhzmgEgC6ttawC5mZGINDEWQR3KiSrIK1-OWHCYIyLMw-TOGa4wBKkjynNB7EqotjhqMgzrFsJ2o-5HlKwjDiLRbwyHhoVNHfZ4ZKIjhNpAgmRBZ6WEcVKwjymyEYZofF9vsBvThBEZ6oqaX3QTIG9GZoFYcaaPeH4yiRzvA9oslYQtC329WEUFzJazkANSBOXrkQEwBhR6ybZKcX3GQjvZnWyEU6UAgN4ZhlgLMs3pGPRlGYX2uswHBAMIQ4yzR-Mm8s730TxcX2rQklOLLjB2hf2QfWAoaJuCpMFZVGSAMrI1lCh1ZqsoUFrP4wvtKB+s6vobLgwG9KB2MgoJaL5lXkBZV8O2awkwiqEJAfoAMDLKRGog1BaXOBDgBuCY5P+JtF9inUHCfUA5h9CitSTZm4NoxAchaW1SqCsVswUIJrr2DAj55AKwXN4DNmpoqNPcsYXORqG9mKyC5wvfdi2OLR+82EfAJmf0jnCQsYEnMxeaBUKRfB15S8zkaiE+E3kgcLDfMaqDTlqNXZsoFedqDXkmzXZW8nWa7L3nJzD5n1Zoq0IoBmg75VdXJFcIbm8C8Gb8kgHxEfnmzDIlspOQfO+EwEXcIsh2WLI-ZDRAFPnGodQM+kEw3KgQVoeKnIC6yeh-DJmr2EfCV0WBCxJXIEFbBhACgegJsYKDaFUKOx7dLgAEDNCIgrAklEIklRRmxlppRsirMhCIXDjGF6jPiKwrHEOBlpewqIYQvSJcp4ovAPqZEGVoBhaFTs6ACHxj7nQiE3CZhdpGWDl45wNZWrEEnCg-8OFhWZ3tIpEJGCj477CvI3Qay04QuSXSdqnIQpPgDCFInAA0HziucS5h2bdlN3SDxA3MHmLzKqDkUULFFai9cWB1xlu0HCegL8F4tbGdkb026Huv2LRlwYxwCgQRVul7FiL0AEiyGFEIoGxKsBsSsjIwUpE9xdgs4z6jkp5B2RRM38rgU4V-5-zbhuE2JaEGaV5KxAnAaAHOA5ZlB+WWQfllUAACkWQVuZMtnJa4aO7YLLmVyIYdhJ62lXpcoqSJlwo2vcfuJRE3CoA2w7uIgOZwIDU4IgpgfQA6B7iQNqQE5WAFQHNhaUOYzSgAPrFcvs9peEQSJKW9YylcqKqs0oqWAqlspg2rugBFD8FYgvwKepsvCjFd3UeANLuPiIRfB+Wk4W3qzh5YrBj8o9c5VQGGX8sOqZADlhyxJX8seW8yk-OIEGVzhWcHLMZWQAmXTKyAsy+ZdRiIRJKGgkoB-rnk+q24QCCgQ+qAKFXZYZgkZCMolTZJcKUq-86MbbkaXDiOlDEqbtAqLL+dGlxwqSAR1zbnQC24QBwAAE1T2lie6D6Oow2JFSWqoZn51Gygy1Gji9APOEmjxACuyMDiO9JJig06cWgW3JDOIZJgCOW8J7kNHyK250O0o+dm2wIEQk6ITkv3GQAABUSavjq4FCApqwIhikFEaDogogdgZ0PgF8pTX0hzKyvDEAQEzUGLcwWY2+CjiLDjcj5yapNfSHpFVrs1dZXDF2Lsg9jfGH0hcUKwTHIQVx6Q1et2sgY7AvluGesbONnr1jxUYYXDLiK6jvZl1IcpCLUSfDQMyAKwBYDMkVJcFsgU3ZHI2ULIKBOAWQZZQ9E1gjCr4JHK1GmoWCoAxVU0y+ut1vpmxH1oQMVWnm+wHh6QcgctYEBfW8031TnD9b8jO5AaCAP6l+tRhgT0g68MG2Ja+oKCOd48EGgNBwCQ2wbVeqwwyIKpQ0KcfcepZVOwmizijcw1q2JY9VS77r-OYYcgNyoZZpKdgs41UlD0YziiQOdEdQMSR4kHhRVKG0DWhvfVj4761gITYCrTxHq5UqITtIsvBgvRyQ2wZmMtFAoQlwgq0Ifnutk1YhNS5IHyr+0wAI9W8CAHaMAliWmB3VFmtlj1nJ4obx22sKXgRoxz4i5I7q86ujlGxzgiEz1dNagFDEExANJpAgEFpjhIbz8jCKYBQEyQFAZgRfSzYCus3TR3NuYTzdxUaXRDGEfmjAKgB2BYQotQ0B1ErxV4JbpIEg14i7DbLurbSOyf0O4uc2sRA8X61AA+rr7frhNmSlntAHQ031xNn6jrc+tiVp5mtwcDgGWtC1WoAN0GkDd1rA0YaBtkGuVLNpG0v0xtra9QNNoi3qA5tsqi+qJvA1LasNsAHDWtrw2EVQ+0MBzYCrS0882y3FcYVlt81X9b0QY5jUVpi1xboA5WhwMPxdIuaiEngNamlEvw7MAtCAMADaMpGdUoNU2ykB5VvSg7UMegYHWyw+0gAP6l24Oqjs4Do7KRCNXHYjuOBstWN+k5HTjpAD+an1CANHRju0kgBgt0GhAGY1rhUBceyS-HcksJ3sBiA9MLoKTtvRY6-tumwTRjhfV6bVIpW3NHIho3uq1e1W+7eqTbTfaBsKke6Mw095d9vNjmTzZoHU0tZdSHYTZWqnOiaDNGmAEUDroDDb4UKo9anaEAQD+b8thW3PDb0428alE50WvrrTV2qhReMwnUlbr+WK7dkBu-pEbr0BssrAtu3fLluMIFaIAgu9JQeAIrmUq2SMQyFJuFV6a6tL6Hvl+GXnj4+NyEXPXe0qo6g5Aqm-XS136TdoII0WMocHohFtl5adGniourzV1KwwzG9jQrsYrhRWhYUVUuxWwAaBXQ0KzTXKiGYl74O4UCMtwzyJDYn4ipXzQ7tQDEiWCcOitQuhMUEwkNqeliVEOe0HhmNu+4vrsEP02jT0f0bkfqm1DGEy9rQwuUvr0Ed60IT22Jc71rWh7F9Gq0bGGE-2ArO+to2-YXzpkHgesKeTMIfWaDQxoDhOaVasX21aiY50ME3MqrMWilxxAQCMHRB7z5FYiSUQbuMNYw95Y9OWvQOoDxVEIWwYgUwMgGuhFa+kca16OqEGWL5HqB2wQKDCegh9TFRxLNTWqMUWKWysCxmPAoGxvUPQEepPCOW972kqIqIIIb4E0Dz7UOiDajC8KUTlgFgbGrlCsB6BfgYs-07UNId6I68Ga8mzHNjTEPQjTsMaoIOAZ6mGQrwoQQUPoE-maRI5yMnTHKq6X3Do4goQcKbKBRIYR6kC8BIYcPG5oeU7hyiO2EGB2YvD-SfRDtKCMHLjagwyRb1lYRXgEJzhojqkhmHQBD0PhzpTNKqQlHD0PMokRAoJncpqjpTOOk92dWoKB1TRhQFTNRBLjkInvFxTkaVIqknasWB0JNE0YFJWlJvNUQbO63szgaDo9RgsBjDxzXhURlSZwgdEei4A8qUocsZ6DpGRmDlB0UUHGOINORChoWTcAFVuapj4Op9eFu32BBHjp26gwwqMkvs1MWBio7wq+DSjQFAh6MgxN0qTjw1GOcGSJgDW4D0FUarlDvQgUdKhDTXEFIDxkDuwkUDgVrTxS5FuylknAAtcaGYBonwodgBwKWug35x2E5AZ6lQHG7UY30iUe2HAB2igiiT6J0k500BiqhNt4UHE6wZgTjqexLJmOJmK9A1ER1RtbcMQQ4DdjJ1-09RKfqgBgJZ17RstGbHnXG1O9uSNdVvLXU4c3FgZJORovplnRiRWJl8t8tzV4mCTvWMMOSdC2UnVE1JhtVKCvBp8nUmfc6sKKowOrX5YQYwG4HrBChD6UqqMnuWjlEF-QkQE2IBq6iYGjiqq0E9VTMwQnAgUJvMU-BjNBm-TkQB0K2DiXINyKIVdhUcQjMJkozYALM3GYBMqrxxjEnOmCZTOqn5sTI308bQrNxh8zsXDQoierXInI0XykXmEE7PN1o+3GsgKMseoi8us0oKIWGDKActI6lRPtfOLVOJ1KKM2R2kzSrCGANsW4jcjMajlzHG5-yKsEoCoAKNpO4CvGc-O+FWlfhaiPwikW117m1EQ5KQOdSWPp0TiXuy012tgJIKiGIDNkUgq3MQHzzeZ5urXJCo-Hsle5-M8OI5bJjOwxZl6F+dzRdE18iiwQBdy-JqKfzyiAcuonyKAWYwwF1yqBehkuHFTXYFqtSX3NWorhQYq8KFFEWwWiCVpOudINqM3kAssWM9JfC4saABkjZkTKmYIDpnoZl5Y01otVmCY8VVpFC+FQfMOi6ILF1nmxKvhlURBAlrsPbmh4OlT0QvIaN4CxCgipRGOIcZ9S6ZOoJyxAPgOUbLPA0bLbAXwPZdWOJypI0RzbPnOIppB7Lj1OyODHvOl07ImgW3nzojBCMpjv8py3gxgmaB3Yeh9mjxYLN9JpQFcwzXAHCAFoErqoJKz2MYlhhRohbbVdRcxIOpq4fERy8eflX-JMSbp3Th5YLMTt5SMlho41b4iGNjgEekUAeH2mp9TQO0MMP1YiAI5BrGAHaCppy7HZtSzi+UnJDPlDGOUAqqvYoBqsoGBxCZOVHpFSvrGYjnCFYGtbADiXJLUpna4oFDmuazM5fAs1zVR1QhQxcqBYGtRLPRk4rCqyyzHHjNAm6zU3O1Y5nMvNndE-qts+daZGgjoGTNTQIBpN0MWf5h53w8VlRk8K4MMNklIbzNB7XOQaAo6uXQQa+cSLmC0NLNGBtmx0FlF-+AKPHDo2W4QVvThvypj3QCbduImFZznorcfd5oE9CDRW1w2rrjJow7AAACKeAQthtb8OTU6rgtGMHABKvhBmrzKQ3HjavAs2w1AF4mz5wZFk3qQFNzWxnXOhVgho2kIW2QHlu5pBy4QD8y9EeB1wpQ5thmy8z0Sq3fcrN1ALctLwc3rAXNjziai+AO2Bb7QBmjGDQBV6iAEt5G9wvmN4Mg7gxHoCpqIDY3i2yt3NPjddvq2XcgF0m34zXMcwKL+t42nCZ3gUDiAcd0eZiG2CO2yWzN12wkPdvs2Ig3tzJNzb9ux3+SOAFuGHegYgARIFk1WrcHOAK5hAq1UQNfmEBssDGegVnXXGca7BD8hFVuiAEkgOBvEbAEUCs28ScVQqE5vgMyDjDWQYVfxsoLAH5awBzAIPTeyRRTrdD6pzK3eyD0mjsoYwzEDllUHvusQjg1cHqqSrft73scgjboGAEkhP8e7rAaZogEHvE5hAfk8B68FYLCA58jaVe0KDZjJopaFQVosRHuDz3TAyDxKQ4FAd92WawtdmtMDmAWT-aZD14EMHLpJRhAogQBkMEexUPGdaDS6RMAge935GnD1gCRx4fgBP+ED25kPaQBwDako9+B0gEIdzCMAwgYlWMHYlCOaZwgerKIFYnD2QpcAUQJZOHthXqwPd58EAA

@BassemHalim
Copy link
Copy Markdown
Contributor Author

BassemHalim commented May 10, 2025

This is just a proof of concept so it's fine if it doesn't get merged I was just tinkering :)

In the last 2 links you provided i was able to load both but when I tried a longer file (~20KB) it didn't load for me before this fix. This is weird because I expected to get a 414 error from cloudflare instead of the infinite loading (or maybe I wasn't patient enough). So the limit seems to be in the browser (I tested with Brave) as well as the server.

Anyway, hash url seems to fix that problem but I am curious why the browser would be able to handle a hash parameter and not a query parameter

This didn't work

very long ~20kb

while this works fine hashed url

@hatemhosny
Copy link
Copy Markdown
Collaborator

I'm getting more convinced with this.

Instead of adding a new getHashParams function and adapt for changes, I suggest modifying getParams function to be something like this:

export const getParams = (
  queryParams = parent.location.search,
  hash = parent.location.hash,
): UrlQueryParams => {
  let params: { [key: string]: string | boolean } = Object.fromEntries(
    new URLSearchParams(queryParams),
  );
  if (hash) {
    hash = hash.slice(1);
    if (hash.startsWith('code/')) {
      params.x = hash;
    }
    if (hash.startsWith('params/')) {
      params.params = hash.slice('params/'.length);
    }
  }
  let encodedParams = {};
  Object.keys(params).forEach((key) => {
    try {
      const value = params[key] as string;
      if (key === 'params') {
        encodedParams = JSON.parse(decompress(value) || '{}');
        if (!encodedParams || typeof encodedParams !== 'object')
          encodedParams = {};
      } else {
        params[key] = decodeURIComponent(value);
      }
    } catch {
      //
    }
    params = { ...encodedParams, ...params };
    if (params[key] === '') params[key] = true;
    if (params[key] === 'true') params[key] = true;
    if (params[key] === 'false') params[key] = false;
  });
  return params;
};

This overrides the params x and params with hash value if present.
otherwise everything works as before.

Then in share function, we change '?x=code/' + compress(JSON.stringify(content)) to '#code/' + compress(JSON.stringify(content))

and in SDK we can change this to:

url.hash = 'params/', compressToEncodedURIComponent(JSON.stringify(params));

We are then left with getPlaygroundUrl SDK function, which will need some re-structuring.

These are still some thoughts. Let's see where this takes us.

@hatemhosny
Copy link
Copy Markdown
Collaborator

or even override them all, so we can store more than 1 value while defaulting to query params.

  if (hash) {
    hash = hash.slice(1);
    const hashParams = Object.fromEntries(new URLSearchParams(hash));
    Object.keys(hashParams).forEach((key) => {
      params[key] = hashParams[key];
    });
  }

@BassemHalim
Copy link
Copy Markdown
Contributor Author

BassemHalim commented May 10, 2025

Cool! I like the idea of overriding params to maintain the backward compatibility.

For the server analytics part, what kind of data do you need in the config? do you only need title and description?
I currently do this

const serverContent = { title: content.title, description: content.description }

  const contentParam = shortUrl
    ? '?x=id/' +
      (await shareService.shareProject({
        ...content,
        result: includeResult ? getCache().result : undefined,
      }))
    : '?x=code/' +compress(JSON.stringify(serverContent)) +'#x=code/' + compress(JSON.stringify(content));

Because I saw in index.ts that that's the only data you are using or should i just clear the code and leave the rest of the metadata as is something like

const serverConfig = {...content, markup: {}, script: {} etc.} // clear code fields (not exact just pseudocode

Finally, how do I test server functions like index.ts above?

@hatemhosny
Copy link
Copy Markdown
Collaborator

Currently we only need title and description for social cards.
We can pass them without compression. This might even be better for SEO.

something like

const params = new URLSearchParams();
if (content.title && content.title !== 'Untitled Project') {
  params.set('title', encodeURIComponent(content.title));
}
if (content.description) {
  params.set('description', encodeURIComponent(content.description));
}
const paramsString = params.toString() ? '?' + params.toString() : '';

const contentParam = shortUrl
  ? '?x=id/' +
    (await shareService.shareProject({
      ...content,
      result: includeResult ? getCache().result : undefined,
    }))
  : paramsString +'#x=code/' + compress(JSON.stringify(content));

you can then receive this data here

- update getParams to accept hash params and default to hash if hash and search params exist
- update getProjectInfo to use new  search params
- update `share` to use new search params
@BassemHalim
Copy link
Copy Markdown
Contributor Author

Hi @hatemhosny

I implemented the following

  • The new url is now `/?title=title&description=Description#x=code/N4IgLglmA2CmIC5xTiA...'
  • Title and description are only available if their values are different from the default
  • the getProjectInfo initializes title and description with the new searchParams if they exist otherwise default to old behavior

Let me know what you think

@BassemHalim
Copy link
Copy Markdown
Contributor Author

sample of the new verrrry long url ~25kB it loads just fine

(sorry if it sends you a long email :) )
verrry long file with title and description params

Copy link
Copy Markdown
Collaborator

@hatemhosny hatemhosny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @BassemHalim

I like how you construct the share URL 👍
I added a comment about overriding the params.

We need to reflect these changes in the SDK: here, here, here and maybe here (I can look into that last one)

please run npm run fix:prettier to fix the failing CI test.

Thank you :)

Comment thread src/livecodes/config/build-config.ts Outdated
);
if (hashParams) { // overwrite params with hash params if they exist
hashParams = hashParams.replace('#', '?');
params = Object.fromEntries(new URLSearchParams(hashParams));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should replace entries not the whole object.

what happens if we do this?
?console=open#x=code/...

I suggest this instead:

params = {
  ...params,
  ...Object.fromEntries(new URLSearchParams(hashParams)),
};

@BassemHalim
Copy link
Copy Markdown
Contributor Author

is it currently possible to share code between livecodes and the sdk? like some shared util functions? I was about to implement getPlaygroundUrl and it's similar to share() in src/livecodes/core.ts so i was thinking of refactoring the common logic of constructing the url. I can just copy the code for now

@hatemhosny
Copy link
Copy Markdown
Collaborator

Yes, you can.

I suggest making a new file (e.g utils.ts) in the sdk directory. Others can import from it.
You may also want to share the same logic in createPlayground and getPlaygroundUrl.

Copy link
Copy Markdown
Collaborator

@hatemhosny hatemhosny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @BassemHalim

What started as a nice-to-have improvement in the share URL is now turning to be a significant refactor for how projects are loaded.
Honestly, I did not like the way it was. I think this will be a real improvement.
We just need to think of all cases and maintain backward compatibility.

I have added some comments (quite a lot, actually 😊). Please have a look and let me what you think.

Also after we settle on the implementation we need to add unit tests for getPlaygroundUrl. It has significant logic now. It also also a pure function that should be easy to test.

Thank you. I appreciate your effort and valuable suggestions.

Comment thread src/sdk/index.ts Outdated
Comment thread src/sdk/index.ts Outdated
throw new Error(`"config" is not a valid URL or configuration object.`);
}
} else if (config && typeof config === 'object' && Object.keys(config).length > 0) {
playgroundUrl.searchParams.set('config', 'sdk'); // fixme: not sure of this one
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was intentional in createPlayground
The config object is then sent separately. See this
Obviously, we should not do that in getPlaygroundUrl.
It would be more appropriate to add it to hash params and remove the configHandler and the eventListener and also remove this.

This messaging was used in the first place to avoid passing the large object in the URL. This PR should solve this in a cleaner way.

Copy link
Copy Markdown
Contributor Author

@BassemHalim BassemHalim May 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the config:sdk params but haven't removed the event handlers yet

Comment thread src/sdk/index.ts Outdated
Comment thread src/sdk/index.ts Outdated
Comment thread src/sdk/index.ts Outdated
Comment thread src/livecodes/core.ts Outdated
Comment thread functions/utils.ts Outdated
Comment thread functions/utils.ts Outdated
Comment thread src/livecodes/core.ts Outdated
Comment thread src/sdk/index.ts
@BassemHalim
Copy link
Copy Markdown
Contributor Author

BassemHalim commented May 16, 2025

Hi @hatemhosny
Yes I realized that after starting. Sorry, it should have mentioned that, yes, it still had some bugs I was just busy the last couple of days and I will work on it tonight I just commit frequently just in case 😅. I planned to add unit tests when done. I am also thinking of using that getPlaygroundUrl in core.ts file so that it acts as the main source of truth but I haven't fully thought of that yet.

My current struggle so far was that some functionality was there to maintain backward comparability and it was a little hard finding the exact behavior of the function but I will ask you if I got stuck.

@hatemhosny
Copy link
Copy Markdown
Collaborator

Thank you @BassemHalim

No worries at all. Your work has pushed me to think more about improvements I planed for quite some time.

Yes, backward-compatibility is very important. We do not want to break our users code. Major breaking changes are always very frustrating.
You may want to refer to the docs to get more details about the SDK.
Of course, also please feel free to ask.

By the way, I have merged a PR (that also improves project loading logic), so please pull the new changes.

@BassemHalim
Copy link
Copy Markdown
Contributor Author

BassemHalim commented May 16, 2025

Hi @hatemhosny
so I fixed getPlaygroundUrl and wrote some unit tests but I am still adding some more. However, I made some modifications in the importCode function that I don't really like at the moment but I am still thinking how to improve it. I will add comments to the parts I don't like.

I am also still testing to make sure i didn't introduce any bugs.

Comment thread src/livecodes/import/import.ts Outdated
): Promise<Partial<Config>> => {
//the url is config=code/...
const searchParams = new URLSearchParams(url);
if (searchParams.get('config') && isCompressedCode(searchParams.get('config') || '')) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I added this to import the code to handle loading from the url but I am not sure why the passed config object is not being used

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not handle this here.

I prefer dealing with config like we deal with other external content, somewhere here. params.config is passed as configUrl. You may want to rename it to something like externalConfig, then check if it is a url to external config json or an encoded config object.

Comment thread src/livecodes/config/build-config.ts
@BassemHalim
Copy link
Copy Markdown
Contributor Author

BassemHalim commented May 18, 2025

Hi @hatemhosny

so I think I like getPlaygroundUrl now and I was thinking of making it the single source of truth for generating urls what do you think? We can also leave that to another PR so that we don't introduce too many changes

so the share function would be this

const share = async (
  shortUrl = false,
  contentOnly = true,
  urlUpdate = true,
  includeResult = false,
  permanentUrl = false,
): Promise<ShareData> => {
  const config = getConfig();
  const content = contentOnly
    ? {
        ...getContentConfig(config),
        markup: {
          ...config.markup,
          title: undefined,
          hideTitle: undefined,
        },
        style: {
          ...config.style,
          title: undefined,
          hideTitle: undefined,
        },
        script: {
          ...config.script,
          title: undefined,
          hideTitle: undefined,
        },
        tools: {
          ...config.tools,
          enabled: defaultConfig.tools.enabled,
          status: config.tools.status === 'none' ? defaultConfig.tools.status : config.tools.status,
        },
      }
    : config;

  const currentUrl = (location.origin + location.pathname).split('/').slice(0, -1).join('/') + '/';
  const appUrl = permanentUrl ? permanentUrlService.getAppUrl() : currentUrl;
  let shareURL = new URL(appUrl);
  if (shortUrl) {
    shareURL.search =
      'x=id/' +
      (await shareService.shareProject({
        ...content,
        result: includeResult ? getCache().result : undefined,
      }));
  } else {
    const playgroundUrl = getPlaygroundUrl({ appUrl, config: content });
    shareURL = new URL(playgroundUrl);
  }

  if (urlUpdate) {
    updateUrl(shareURL.href, true);
  }

  const projectTitle = content.title !== defaultConfig.title ? content.title + ' - ' : '';

  return {
    title: projectTitle + 'LiveCodes',
    url: shareURL.href,
  };
};

The url structure so far is short params in search params for server analytics and longer ones (config and params) in hash params
https://livecodes.io/?title=Title&description=description&x=id/{dpaste id}#config=code/{compressed config}&params={compressed params}

Copy link
Copy Markdown
Collaborator

@hatemhosny hatemhosny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @BassemHalim
I like this.

I have added some comments, the most significant is how we deal with the encoded config object. Please let me know if you need more guidance.

so I think I like getPlaygroundUrl now and I was thinking of making it the single source of truth for generating urls what do you think?

I think this is reasonable. Please go ahead.

Comment thread src/livecodes/import/import.ts Outdated
): Promise<Partial<Config>> => {
//the url is config=code/...
const searchParams = new URLSearchParams(url);
if (searchParams.get('config') && isCompressedCode(searchParams.get('config') || '')) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not handle this here.

I prefer dealing with config like we deal with other external content, somewhere here. params.config is passed as configUrl. You may want to rename it to something like externalConfig, then check if it is a url to external config json or an encoded config object.

Comment thread src/livecodes/index.ts Outdated
Comment thread src/sdk/index.ts Outdated
Comment thread src/sdk/index.ts Outdated
Comment thread src/sdk/index.ts Outdated
Comment thread src/sdk/index.ts
- moved handling compressed config to importExternalContent
- reverted removing config event listener for backward compatibility
- set `origin` variable in `createPlayground`
Comment thread src/livecodes/core.ts
notifications.error(
window.deps.translateString('core.error.invalidImport', 'Invalid import URL'),
);
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I moved handling the compressed config here but I had to move the "Invalid import URL" warning here because importCode() gets passed the hash parameter as url (I'm assuming this used to be a feature) and the function fails to import and returns an empty object. So now I updated the warning to be thrown if urlConfig is an empty object and configUrlConfig is undefined

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed earlier on, I used the hash as the import url source then moved to the query param x (for social cards and analytics).

@hatemhosny
Copy link
Copy Markdown
Collaborator

Thank you @BassemHalim
It is shaping up.

Please wrap the last block in the SDK with this IIFE for tree-shaking, otherwise the whole file ends up in the bundle:

/* @__PURE__ */ (() => {
// the code here
})();

Sorting imports is beneficial, but please later on keep it in a separate commit because it gets difficult to see what changed.

Please see if you want to add something else, othewise I think this is almost ready to merge.
Maybe I will add a few things after you finish.

Thank you.

@BassemHalim BassemHalim marked this pull request as ready for review May 20, 2025 18:42
@BassemHalim
Copy link
Copy Markdown
Contributor Author

BassemHalim commented May 20, 2025

Hi Dr. @hatemhosny
I just added the __PURE__ comment and ran prettier. I am learning a lot in this project; I haven't used the __PURE__ comment before. Thank you for your suggestions and guidance.
Sorry about the sorted imports I have an extension and I only realized it ran on this file after pushing

I don't think I have anything to add to this PR at the moment but I am planing to open another PR later to refactor core.ts since it is too big. I will open an issue if you are open to the idea.

@hatemhosny
Copy link
Copy Markdown
Collaborator

I haven't used the __PURE__ comment before.

Have a look here: https://esbuild.github.io/api/#pure

Also try removing it and check the bundle size
in build/livecodes: app.js, embed.js and headless.js
and the minified version: run npm run build:app and check in the same location (files are hashed)

Sorry about the sorted imports I have an extension and I only realized it ran on this file after pushing

No worries at all. It might be a good idea to automate it. Maybe something like this: https://www.npmjs.com/package/prettier-plugin-organize-imports
If you are interested, I'd be happy to have a PR

I don't think I have anything to add to this PR at the moment but I am planing to open another PR later to refactor core.ts since it is too big. I will open an issue if you are open to the idea.

I'm open to improvements. Let's discuss that in an issue first.

I will add a few things here before merge. I'd like to have your opinion.

Comment thread src/livecodes/index.ts Fixed
@hatemhosny
Copy link
Copy Markdown
Collaborator

hi @BassemHalim

Now the SDK and the app communicate their versions (even if self-hosted, regardless of the deployment URL)
Based on that we check for version and provide backward-compatible way to send the embed options (including config & params)

Demos:
old sdk + new app: https://livecodes.io/?x=id/qcnrzidxfjy
new sdk + old app: https://livecodes.io/?x=id/x8zazst8ts8
new sdk + new app: https://livecodes.io/?x=id/zqxgk9ie8kz

I also added CORS headers for netlify (for these demos to work!)

Please have a look when you have time and let me know what you think.

@sonarqubecloud
Copy link
Copy Markdown

@hatemhosny
Copy link
Copy Markdown
Collaborator

Added a note in docs about using multiple sources in embed options:
template, import, config, params

https://deploy-preview-819--livecodes.netlify.app/docs/sdk/js-ts#multiple-sources

@BassemHalim
Copy link
Copy Markdown
Contributor Author

I tested the newer version and everything is working perfectly.

So from my understanding, now createPlayground always sets config:sdk and then a new param sdkVersion is introduced to decide how config is sent. I am not sure why the newer sdk needs to set the config param? is it for the old apps? because I was thinking the new sdk doesn't need that but the new app needs to be able to handle the config:sdk param.
for example, here maybe we can check if !sdkVersion || sdkVersion < 46

 // for backward-compatibility
  if (typeof config === 'object' && Object.keys(config).length > 0) {
    playgroundUrl.searchParams.set('config', 'sdk');
  }

Also I a naive question but I am curious, when do old urls get retired? will the backward compatibility code always exist or maybe if a feature has been retired for x amount of time it gets dropped? I was thinking maybe we show a warning and update the browser url letting the user know to copy the new url.

@hatemhosny
Copy link
Copy Markdown
Collaborator

hatemhosny commented May 21, 2025

Thank you @BassemHalim for taking the time to test it.

So the aim is to preserve backward compatibility for older SDK or older apps.

Users can have old SDK (installed from npm or via a CDN URL) and no appUrl, which defaults to https://livecodes.io/ which will run a new version when we release. This can break users code even if they have not done any changes.

The other scenario is upgrading the SDK from npm while the appUrl in code still points to an old app. Please note that setting the appUrl is the recommended way to avoid breaking changes (e.g. when we upgrade compilers, etc). We want to make a good upgrade experience, otherwise users will not upgrade. Also please note that it might not just be a matter of changing the version number in appUrl. Users may point the appUrl to a self-hosted instance, which will require a new deployment to upgrade.

So ideally, old URLs should keep working forever! (obviously as much as we can). At least now we setup a way of communication of versions between the SDK and the app, so that when the maintenance burden increases too much we can start showing a warning in the console to encourage upgrade.

The SDK version is sent to the app in the sdkVersion query param. This is a part of the iframe src url.

The app version is sent by a postMessage once the app starts loading.
By that time, the playground URL and all params have already been used, so we cannot set any query params (including the config:sdk) based on app version.

That's why we have to keep sending config and other params, because this is what old apps required.

However, the new app will not ask the SDK to send the config object (the old way), and the new SDK will not send it.

Have I answered your questions?
I'm open to suggestions in case you think there is a better way to achieve that.

@hatemhosny
Copy link
Copy Markdown
Collaborator

hatemhosny commented May 21, 2025

Another section in LiveCodes that relies on generating URLs to playgrounds is the embed screen

Currently it generates a short share URL and uses it in the code snippets.
This has 2 problems:

  • If the app is an official LiveCodes app (e.g. *.livecodes.io), this uses our share service. However, if it is a self-hosted instance, dpaste service is used. The problem is that dpaste links expire after 1 year, so these embedded playgrounds will stop working.
  • LiveCodes philosophy is that we focus on privacy. The is from the docs homepage:

Projects are private by default. The code you write in LiveCodes never leaves your device, unless you choose to share, export or sync it.

I prefer to refactor that to use the SDK in a better way, while still giving the option to use short URLs (only in our hosted platform).
But let's keep this to another PR.

So now we have plans for 3 PRs:

  • refactor core.ts (still needs some discussion)
  • embed screen
  • auto-sort imports

@BassemHalim
Copy link
Copy Markdown
Contributor Author

BassemHalim commented May 21, 2025

That clarified it for me. Thank you! I don't currently have any suggestions for this PR

Regarding the new PR plans, I think auto-sorting would be a short one so i can do that. Then, I can open an issue to discuss refactoring core.ts and adding documentation and more tests to ensure nothing breaks. This would hopefully simplify updating embed screen

@hatemhosny hatemhosny merged commit 20eb9b8 into live-codes:develop May 21, 2025
15 checks passed
@hatemhosny hatemhosny mentioned this pull request May 22, 2025
12 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants