{"id":51883347,"url":"https://github.com/eviltik/lit-isoflow","last_synced_at":"2026-07-25T16:03:30.044Z","repository":{"id":371124330,"uuid":"1299286023","full_name":"eviltik/lit-isoflow","owner":"eviltik","description":"Viewer and editor for isometric infrastructure diagrams, as a Lit web component — a React-free port of Isoflow/FossFLOW","archived":false,"fork":false,"pushed_at":"2026-07-13T14:13:33.000Z","size":550,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-07-13T15:07:02.706Z","etag":null,"topics":["diagram","fossflow","infrastructure-diagram","isoflow","isometric","lit","lit-element","network-diagram","web-components"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eviltik.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-13T12:48:31.000Z","updated_at":"2026-07-13T14:16:53.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/eviltik/lit-isoflow","commit_stats":null,"previous_names":["eviltik/lit-isoflow"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/eviltik/lit-isoflow","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eviltik%2Flit-isoflow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eviltik%2Flit-isoflow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eviltik%2Flit-isoflow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eviltik%2Flit-isoflow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eviltik","download_url":"https://codeload.github.com/eviltik/lit-isoflow/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eviltik%2Flit-isoflow/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35884178,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-25T02:00:06.922Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["diagram","fossflow","infrastructure-diagram","isoflow","isometric","lit","lit-element","network-diagram","web-components"],"created_at":"2026-07-25T16:03:28.953Z","updated_at":"2026-07-25T16:03:30.033Z","avatar_url":"https://github.com/eviltik.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# lit-isoflow\n\n[![CI](https://github.com/eviltik/lit-isoflow/actions/workflows/ci.yml/badge.svg)](https://github.com/eviltik/lit-isoflow/actions/workflows/ci.yml)\n[![npm](https://img.shields.io/npm/v/lit-isoflow.svg)](https://www.npmjs.com/package/lit-isoflow)\n[![licence: MIT](https://img.shields.io/badge/licence-MIT-blue.svg)](LICENSE)\n[![no dependencies but three](https://img.shields.io/badge/deps-lit%20%C2%B7%20zod%20%C2%B7%20pathfinding-informational)](package.json)\n\n**Viewer and editor for isometric infrastructure diagrams**, as a single Lit\nweb component — a React-free port of\n[Isoflow](https://github.com/markmanx/isoflow) / FossFLOW.\n\nThe model format (JSON) is interchangeable with Isoflow/FossFLOW exports: diagrams\ncreated there render here, and vice versa.\n\n### ▶ [Try the live demos](https://eviltik.github.io/lit-isoflow/)\n\nTwo of them: the [editor](https://eviltik.github.io/lit-isoflow/editor/) (the full\ncomponent) and a [stress test](https://eviltik.github.io/lit-isoflow/stress/) (up\nto 10 000 nodes, with the numbers).\n\n[![lit-isoflow editor](docs/assets/screenshot-editor.png)](https://eviltik.github.io/lit-isoflow/)\n\n## Why\n\nThe upstream Isoflow is an excellent piece of work, but it is a React application:\nReact, MUI, zustand, immer, gsap, chroma-js, react-quill — over 1 MB of\ndependencies before your own code. If your app is not a React app, you cannot\nembed it.\n\n`lit-isoflow` keeps the parts that matter — the isometric projection, the A\\*\nconnector routing, the model format — and rewrites the view layer as a standard\nweb component. Three runtime dependencies (`lit`, `zod`, `pathfinding`), roughly\n140 kB bundled, and it drops into any framework, or none.\n\n## One component, three modes\n\nThe `editor-mode` attribute selects what `\u003clit-isoflow\u003e` is:\n\n| Mode                              | What you get                                                                            | Typical use                                             |\n| --------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------- |\n| `EXPLORABLE_READONLY` _(default)_ | **Viewer** — pan, zoom, fit-to-view; the model is never mutated                         | embedding a diagram in docs, dashboards, read-only apps |\n| `EDITABLE`                        | **Editor** — everything below: tools, selection, drag, drawing, undo/redo, property API | diagram authoring UI                                    |\n| `NON_INTERACTIVE`                 | **Static rendering** — no listeners at all                                              | screenshots, PDF/PNG export pipelines, thumbnails       |\n\n```html\n\u003c!-- Viewer --\u003e\n\u003clit-isoflow fit-to-view\u003e\u003c/lit-isoflow\u003e\n\n\u003c!-- Editor --\u003e\n\u003clit-isoflow editor-mode=\"EDITABLE\" fit-to-view\u003e\u003c/lit-isoflow\u003e\n```\n\nEditing capabilities (`EDITABLE`): select \u0026 drag items, rubber-band selection\n(drag on empty canvas to select a group, then move it as one; Shift adds to the\nselection — click or band), click-through to elements buried under others\n(clicking the selected element again descends the tile's stack), draw connectors\n(anchored to items or tiles), re-anchor or bend connectors by dragging their\nanchors/path, draw \u0026 resize rectangles, place icons, add text boxes, delete\nselection, gesture-level undo/redo, transient pan (hold Ctrl/Space).\nProperty panels (name, color, description…) are provided by the host app —\nsee “Wiring a property panel”.\n\nRendering (all modes): grid, nodes (isometric \u0026 flat icons), connectors\n(A* routing, solid/dashed/dotted, direction arrows, labels), rectangles,\ntext boxes, node labels.\n\n## Install\n\n```bash\nnpm install lit-isoflow\n```\n\n## Usage\n\n```html\n\u003clit-isoflow fit-to-view style=\"width: 100%; height: 600px\"\u003e\u003c/lit-isoflow\u003e\n\n\u003cscript type=\"module\"\u003e\n  import 'lit-isoflow';\n\n  const diagram = document.querySelector('lit-isoflow');\n  diagram.model = {\n    title: 'My diagram',\n    icons: [{ id: 'server', name: 'Server', url: '...', isIsometric: true }],\n    colors: [{ id: 'blue', value: '#a5b8f3' }],\n    items: [{ id: 'srv1', name: 'Server 1', icon: 'server' }],\n    views: [\n      {\n        id: 'main',\n        name: 'Main view',\n        items: [{ id: 'srv1', tile: { x: 0, y: 0 } }],\n        connectors: [],\n        rectangles: [],\n        textBoxes: []\n      }\n    ]\n  };\n\u003c/script\u003e\n```\n\nPutting it into a real app — bundling, icon packs, saving, theming, rendering\ndiagrams into PDFs and Word files — is covered in the\n**[integration guide](docs/integration.md)**.\n\n### Properties / attributes\n\n| Property          | Attribute          | Default               | Description                                                          |\n| ----------------- | ------------------ | --------------------- | -------------------------------------------------------------------- |\n| `model`           | —                  | `null`                | Diagram model (Isoflow/FossFLOW JSON)                                |\n| `viewId`          | `view-id`          | first view            | View to display                                                      |\n| `editorMode`      | `editor-mode`      | `EXPLORABLE_READONLY` | `EDITABLE`, `EXPLORABLE_READONLY` (pan/zoom) or `NON_INTERACTIVE`    |\n| `showGrid`        | `show-grid`        | `true`                | Show the isometric grid                                              |\n| `theme`           | `theme`            | `auto`                | `light`, `dark`, or `auto` (follows the OS) — see Theming            |\n| `backgroundColor` | `background-color` | —                     | Overrides the theme's background                                     |\n| `fitToView`       | `fit-to-view`      | `false`               | Fit the view in the viewport on load                                 |\n| `strings`         | —                  | English               | Overrides for the component's two strings — see Internationalisation |\n\n### Methods\n\n- `zoomIn()` / `zoomOut()` — multiplicative steps (×1.25), from 0.01 to 4. The\n  floor is that low on purpose: a diagram of several thousand nodes only fits on\n  screen at a few percent.\n- `fit()` — fit the whole view inside the viewport, measured on what is actually\n  painted (icons, labels, connector paths) rather than the tile bounding box,\n  which overestimates by roughly 3×\n- `setTool(tool, options?)` — activate an editing tool: `'CURSOR'`, `'PAN'`,\n  `'PLACE_ICON'` (`options.iconId`), `'CONNECTOR'`, `'RECTANGLE'`, `'TEXTBOX'`\n- `tool` (getter) — currently active tool\n- `deleteSelection()` — delete the selected item (also bound to the Delete key)\n- `clearSelection()` — clear the selection (also bound to Escape); the host can\n  use it to close its property panel\n- `undo()` / `redo()` — gesture-level history (also bound to Ctrl+Z / Ctrl+Y /\n  Ctrl+Shift+Z); `canUndo` / `canRedo` getters\n- `getModel()` — deep snapshot of the current (possibly edited) model\n- `getSelectedItem()` / `updateItem()` / `updateViewItem()` / `updateConnector()`\n  / `updateRectangle()` / `updateTextBox()` — property-panel API, see\n  “Wiring a property panel” below\n- `getSelectedItems()` — the rubber-band selection, as `[{ type, id }]` (or\n  `null`). Exclusive with `getSelectedItem()`: a group has no property panel,\n  a plain click on a member collapses the group to that single element\n- `createRectangle({ from, to, color?, id? })` / `deleteRectangle(id)` — place a\n  zone programmatically (importing, templating, generating), rather than only by\n  drawing it with the mouse. Returns the new id.\n- `exportSvg(options?)` — renders the view to **vector SVG**. Returns\n  `{ svg, width, height }`. Options: `showGrid` (default false), `background`\n  (default `'transparent'`), `margin` (default 0.15 tiles). A thin wrapper around\n  `renderToSvg()` — see “Rendering without a browser”.\n- `exportPng(options?)` — renders the view to a PNG with no extra dependency\n  (off-screen clone → SVG `foreignObject` → canvas), cropped tightly to the\n  rendered content (icons, labels, connectors — not the tile bounding box).\n  Returns `{ blob, dataUrl, width, height }`. Options: `scale` (default 2),\n  `showGrid` (default false), `background` (any CSS color, including\n  `'transparent'`), `margin` (tiles around the content, default 0.15).\n  Icon URLs must be data URIs or same-origin (external images would taint\n  the canvas); isopack icons are data URIs, so they just work.\n\n### Keyboard (EDITABLE)\n\n| Key                           | Action                                                      |\n| ----------------------------- | ----------------------------------------------------------- |\n| Hold **Ctrl** or **Space**    | Pan; the active tool and selection are restored on release  |\n| **Shift+click**               | Add an element to the selection, or remove it (toggle)      |\n| **Shift+drag** (anywhere)     | Rubber band that adds to the selection instead of replacing |\n| Click the selected element    | Select the next element below it in the tile's stack        |\n| **Delete** / **Backspace**    | Delete the selection                                        |\n| **Escape**                    | Clear the selection                                         |\n| **Ctrl+Z**                    | Undo                                                        |\n| **Ctrl+Y** / **Ctrl+Shift+Z** | Redo                                                        |\n\nShortcuts are ignored while typing in an input, including inside a shadow root.\n\n### Events\n\n- `diagram-ready` — model parsed and scene rendered\n- `zoom-changed` — `detail.zoom`\n- `model-error` — `detail.error` (zod validation error)\n- `item-selected` — `detail.item` (`{ type, id }` or `null`)\n- `selection-changed` — `detail.items` (rubber-band selection, `[{ type, id }]` or `null`)\n- `model-updated` — `detail.model` (debounced snapshot after each edit)\n- `tool-changed` — `detail.tool`\n- `history-changed` — `detail.canUndo` / `detail.canRedo`\n\n### Wiring a property panel / rich text editor\n\n`\u003clit-isoflow\u003e` deliberately ships **no property panel and no rich text\neditor**: the canvas stays lean (lit + zod + pathfinding) and the host app\nbrings its own UI kit. The wiring contract is three parts:\n\n1. **Listen to `item-selected`**, then call `getSelectedItem()` for the full\n   data of the selection:\n\n   ```js\n   diagram.addEventListener('item-selected', () =\u003e {\n     const selected = diagram.getSelectedItem();\n     // ITEM      → { type, id, modelItem: { name, description, icon }, viewItem: { tile, labelHeight } }\n     // CONNECTOR → { type, id, connector: { description, color, style, width, anchors } }\n     // RECTANGLE → { type, id, rectangle: { color, from, to } }\n     // TEXTBOX   → { type, id, textBox: { content, fontSize, orientation, tile } }\n     renderMyPanel(selected); // null when the selection is cleared\n   });\n   ```\n\n2. **Write changes back** through the update methods — each call re-renders\n   the scene, feeds the undo history and emits `model-updated`:\n\n   ```js\n   diagram.updateItem(id, { name, description, icon });\n   diagram.updateViewItem(id, { tile, labelHeight });\n   diagram.updateConnector(id, { description, color, style, width });\n   diagram.updateRectangle(id, { color });\n   diagram.updateTextBox(id, { content, fontSize, orientation });\n   ```\n\n3. **Persist** by listening to `model-updated` (debounced) or calling\n   `getModel()` whenever you save.\n\nThe editor demo ([demo/editor/index.html](demo/editor/index.html)) implements a\ncomplete panel with plain HTML inputs — no dependency — and is the reference\nexample.\n\n**Rich descriptions:** `modelItem.description` is an **HTML string**\n(≤ 1000 chars; upstream Isoflow edits it with Quill and uses `\u003cp\u003e\u003cbr\u003e\u003c/p\u003e` as\nits empty value). Any editor that produces HTML plugs in. With tiptap:\n\n```js\nimport { Editor } from '@tiptap/core';\nimport StarterKit from '@tiptap/starter-kit';\n\nlet editor;\ndiagram.addEventListener('item-selected', () =\u003e {\n  const selected = diagram.getSelectedItem();\n  editor?.destroy();\n  if (selected?.type !== 'ITEM') return;\n\n  editor = new Editor({\n    element: document.querySelector('#description-editor'),\n    extensions: [StarterKit],\n    content: selected.modelItem.description ?? '',\n    onUpdate: ({ editor }) =\u003e {\n      diagram.updateItem(selected.id, { description: editor.getHTML() });\n    }\n  });\n});\n```\n\nNotes:\n\n- Each keystroke burst (pauses \u003c 250 ms) collapses into one undo step; debounce\n  `onUpdate` yourself if you want coarser steps.\n- Descriptions are rendered as-is in node labels — sanitize if models come from\n  untrusted sources.\n\n### Icons\n\nIcons are plain image URLs (SVG/PNG, data URIs welcome), declared in `model.icons`.\n`isIsometric: true` renders the image as-is (pre-projected isometric artwork);\n`isIsometric: false` projects a flat image onto the isometric ground plane.\n\nThe official icon packs work unchanged — the demo loads all five\n[@isoflow/isopacks](https://www.npmjs.com/package/@isoflow/isopacks)\n(Isoflow basic, AWS, Azure, GCP, Kubernetes — 1000+ icons) and exposes them in\na searchable gallery:\n\n```js\nimport isoflowIsopack from '@isoflow/isopacks/dist/isoflow';\n\nconst icons = isoflowIsopack.icons.map((icon) =\u003e ({ ...icon, collection: 'Isoflow' }));\ndiagram.model = { ...model, icons };\n```\n\nIcon artwork belongs to its respective owners (AWS, Microsoft, Google, CNCF,\nIsoflow); check the isopacks repository for per-collection licences. That is\nwhy `@isoflow/isopacks` is a dev-dependency of the demo, not a dependency of\nthis package.\n\n### Theming (light / dark)\n\n```html\n\u003clit-isoflow theme=\"dark\"\u003e\u003c/lit-isoflow\u003e\n\u003c!-- light | dark | auto --\u003e\n```\n\n`auto` (the default) follows the OS via `prefers-color-scheme` and repaints when\nit changes. In an app that has its own theme toggle, drive the property instead —\n`auto` would ignore it:\n\n```js\ndiagram.theme = app.isDark ? 'dark' : 'light';\n```\n\n**The theme only repaints the chrome** — background, grid, label boxes, leader\nlines, connector halos, controls. Node, connector and zone colours come from\n`model.colors`: they belong to the diagram, not to the interface, and a diagram\nthat means something in red still means it at night.\n\nThe same applies to the headless renderer, which defaults to `light` — an export\nis a document, not a screen, so it must not depend on the machine that generated it:\n\n```js\nrenderToSvg(model, { theme: 'dark' });\n```\n\nBoth worlds read the same palettes ([src/theme.js](src/theme.js)), so the canvas\nand the exported SVG cannot drift apart.\n\n### Rendering without a browser\n\nThe SVG renderer is **pure JavaScript with no DOM**, so a diagram can be turned\ninto vector output anywhere JavaScript runs — a CLI, a build step, a server, a\nPDF pipeline:\n\n```js\nimport { renderToSvg } from 'lit-isoflow/render'; // no lit, no DOM\n\nconst { svg, width, height } = renderToSvg(model, {\n  theme: 'light', // 'light' (default) or 'dark'\n  background: '#ffffff', // overrides the theme's background; 'transparent' by default\n  showGrid: false,\n  margin: 0.5 // tiles of padding around the content\n});\n```\n\nThis entry point pulls in neither Lit nor the component, so it stays light in a\nNode process. It shares the geometry engine with `\u003clit-isoflow\u003e` — the component's\n`exportSvg()` is exactly this function — so what you get is what the editor shows.\n\nNotes:\n\n- Node descriptions (HTML) are flattened to plain text, since SVG has no rich text.\n- Text width is measured with a canvas in the browser, and estimated from font\n  metrics in Node, so text-box layouts can differ by a pixel or two.\n- Icon aspect ratios are read from their data-URI `viewBox`. For raster icons,\n  pass `iconSizes: { [iconId]: { width, height } }`.\n\n### Internationalisation\n\nThe component is a canvas: it owns exactly **two** user-facing strings, so there\nis no i18n layer to configure — just override them:\n\n```js\ndiagram.strings = {\n  untitledItem: 'Sans titre', // name given to a newly placed node\n  invalidModel: 'Modèle invalide' // prefix of the model validation error\n};\n```\n\nEverything else on screen comes from your model (node names, labels) or from your\nown UI. The demo ships a ~40-line, dependency-free i18n module\n([demo/shared/i18n.js](demo/shared/i18n.js)) — dictionary per language, `t()`\nhelper, browser detection, `localStorage` persistence — as a reference for host\napps.\n\n## Performance\n\nNodes are real DOM subtrees (roughly nine elements each, three without a label),\nwhich buys hit-testing, CSS, and accessibility — and sets a ceiling. Two things\nkeep that ceiling far away:\n\n- **Off-screen nodes are not mounted.** Only what the viewport shows is in the\n  DOM.\n- **A node's template is rebuilt only when that node changed.** Its rendering\n  depends on exactly four things — view item, model item, resolved icon, theme —\n  and mutations replace those objects rather than editing them in place, so\n  identity is a sound change signal.\n\nTogether, they make interaction **independent of model size**. Measured with the\n[stress demo](https://eviltik.github.io/lit-isoflow/stress/) at the working zoom,\n1 000 and 10 000 nodes both hold ~60 fps while panning and under live mutation\n(~3 000 icon swaps per second).\n\nThe honest limit: **zoom all the way out to fit a huge diagram and every node is\non screen, so every node is mounted.** Reconciling 10 000 live DOM subtrees costs\n~120 ms per frame no matter how little has changed — around 2 fps. If you need to\nfly over diagrams that big, this is the wall, and the answer is level-of-detail\n(paint a dot, not a subtree, when an icon is 7 px wide), not a faster renderer.\n\nRun the numbers yourself: the stress demo generates up to 10 000 nodes and reports\nfirst render, DOM size, and frame rate — including the bad ones.\n\n## Security\n\nNode `description` fields hold **HTML** (that is what Isoflow's rich text editor\nproduces) and are rendered as-is inside node labels. Sanitize them if your models\ncome from untrusted sources.\n\n## Development\n\n```bash\ngit clone https://github.com/eviltik/lit-isoflow\ncd lit-isoflow\npnpm install\npnpm run demo     # the demo doubles as the manual test bench\npnpm run check    # lint + formatting + unit tests\n```\n\nThere is no build step: `src/` is plain ES modules with JSDoc types, and that is\nwhat gets published. See [CONTRIBUTING.md](CONTRIBUTING.md) — and if you are an\nAI coding agent (or pair with one), [AGENTS.md](AGENTS.md) maps the source,\nlists the invariants that must not break, and documents the e2e testing traps.\n\n## Design notes / deviations from Isoflow\n\n- **No React, MUI, zustand, immer, gsap, chroma-js or dom-to-image.** The geometry\n  engine (projection, A\\* connector routing, fit-to-view) is ported as-is; the view\n  layer is rewritten with Lit templates, animations use CSS transitions, colour\n  variants use an HSL approximation of chroma's brighten/darken, and PNG export\n  goes through an SVG `foreignObject`.\n- The scene (connector paths, textbox sizes) is derived from the model as a pure\n  function (`deriveScene`) instead of being kept in sync inside a store.\n- Selected-connector anchors render above the nodes layer and take hit-test\n  priority, so endpoints sitting on a node can be grabbed and re-anchored\n  (upstream hides them behind nodes and always drags the node).\n- Gesture-level undo/redo — absent upstream.\n- Invalid connectors are skipped rather than deleted from the model.\n- The rubber-band selection is lit-isoflow's own, not a port: upstream's Lasso\n  mode is entirely commented out. The band is a screen-aligned rectangle, and\n  capture follows the eye: an element is selected when it falls inside the\n  rectangle on screen.\n\n## Credits\n\nThis is a port, and the hard thinking — the isometric projection, the connector\nrouting, the model format, the icon packs — comes from the original work:\n\n- [Isoflow](https://github.com/markmanx/isoflow) by Mark Mankarious (MIT)\n- [FossFLOW](https://github.com/stan-smith/FossFLOW) by Stan Smith (MIT), which\n  keeps the community edition alive\n- [@isoflow/isopacks](https://www.npmjs.com/package/@isoflow/isopacks) for the icons\n\n## Licence\n\nMIT — see [LICENSE](LICENSE). Portions derived from Isoflow (MIT) and FossFLOW\n(MIT). Icon artwork in the demo belongs to its respective owners.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feviltik%2Flit-isoflow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feviltik%2Flit-isoflow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feviltik%2Flit-isoflow/lists"}