Run Prettier across projects

Problem:
Prettier was not running when I saved Emacs buffers.

Why?
- prettier-js-mode needs needs node; lorri exposes node to direnv; direnv
  exposes node to Emacs; lorri was not working as expected.

Solution:
Now that I'm using nix-buffer, I can properly expose node (and other
dependencies) to my Emacs buffers. Now Prettier is working.

Commentary:
Since prettier hadn't worked for so long, I stopped thinking about it. As such,
I did not include it as a dependency in boilerplate/typescript. I added it
now. I retroactively ran prettier across a few of my frontend projects to unify
the code styling.

I may need to run...
```shell
$ cd ~/briefcase
$ nix-shell
$ npx prettier --list-different "**/*.{js,ts,jsx,tsx,html,css,json}"
```
...to see which files I should have formatted.
This commit is contained in:
William Carroll 2020-03-27 10:52:13 +00:00
parent f4f7f454fa
commit 514136c99a
22 changed files with 181 additions and 128 deletions

View file

@ -4,11 +4,13 @@
"main": "index.js", "main": "index.js",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"dev": "npx parcel src/index.html & npx tsc --watch --noEmit" "dev": "parcel src/index.html & npx tsc --watch --noEmit",
"prettier": "prettier --ignore-path .gitignore --write \"**/*.{js,ts,jsx,tsx,html,css.json}\""
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^13.9.3", "@types/node": "^13.9.3",
"parcel-bundler": "^1.12.4", "parcel-bundler": "^1.12.4",
"prettier": "^2.0.2",
"tailwindcss": "^1.2.0", "tailwindcss": "^1.2.0",
"typescript": "^3.8.3" "typescript": "^3.8.3"
}, },

View file

@ -1,8 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<link rel="stylesheet" href="./index.css"> <link rel="stylesheet" href="./index.css" />
</head> </head>
<body> <body>
<div id="mount"></div> <div id="mount"></div>

View file

@ -4287,6 +4287,11 @@ prelude-ls@~1.1.2:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
prettier@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.2.tgz#1ba8f3eb92231e769b7fcd7cb73ae1b6b74ade08"
integrity sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg==
pretty-hrtime@^1.0.3: pretty-hrtime@^1.0.3:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"

View file

@ -28,6 +28,12 @@
"integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==",
"dev": true "dev": true
}, },
"prettier": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.2.tgz",
"integrity": "sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg==",
"dev": true
},
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",

View file

@ -9,6 +9,7 @@
"author": "William Carroll", "author": "William Carroll",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"prettier": "^2.0.2",
"ts-node": "^8.6.2", "ts-node": "^8.6.2",
"typescript": "^3.7.5" "typescript": "^3.7.5"
} }

View file

@ -8,7 +8,7 @@ function sortScores(xs: Array<number>, highest: number): Array<number> {
} }
for (let i = 0; i < xs.length; i += 1) { for (let i = 0; i < xs.length; i += 1) {
counts[xs[i]] += 1 counts[xs[i]] += 1;
} }
for (let i = highest; i >= 0; i -= 1) { for (let i = highest; i >= 0; i -= 1) {
@ -22,29 +22,28 @@ function sortScores(xs: Array<number>, highest: number): Array<number> {
return result; return result;
} }
// Tests // Tests
let desc = 'no scores'; let desc = "no scores";
let actual = sortScores([], 100); let actual = sortScores([], 100);
let expected = []; let expected = [];
assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc); assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc);
desc = 'one score'; desc = "one score";
actual = sortScores([55], 100); actual = sortScores([55], 100);
expected = [55]; expected = [55];
assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc); assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc);
desc = 'two scores'; desc = "two scores";
actual = sortScores([30, 60], 100); actual = sortScores([30, 60], 100);
expected = [60, 30]; expected = [60, 30];
assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc); assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc);
desc = 'many scores'; desc = "many scores";
actual = sortScores([37, 89, 41, 65, 91, 53], 100); actual = sortScores([37, 89, 41, 65, 91, 53], 100);
expected = [91, 89, 65, 53, 41, 37]; expected = [91, 89, 65, 53, 41, 37];
assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc); assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc);
desc = 'repeated scores'; desc = "repeated scores";
actual = sortScores([20, 10, 30, 30, 10, 20], 100); actual = sortScores([20, 10, 30, 30, 10, 20], 100);
expected = [30, 30, 20, 20, 10, 10]; expected = [30, 30, 20, 20, 10, 10];
assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc); assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc);

View file

@ -4,11 +4,13 @@
"main": "index.js", "main": "index.js",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"dev": "npx parcel src/index.html & npx tsc --watch --noEmit" "dev": "parcel src/index.html & npx tsc --watch --noEmit",
"prettier": "prettier --ignore-path .gitignore --write \"**/*.{js,ts,jsx,tsx,html,css.json}\""
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^13.9.3", "@types/node": "^13.9.3",
"parcel-bundler": "^1.12.4", "parcel-bundler": "^1.12.4",
"prettier": "^2.0.2",
"tailwindcss": "^1.2.0", "tailwindcss": "^1.2.0",
"typescript": "^3.8.3" "typescript": "^3.8.3"
}, },

View file

@ -1,7 +1,5 @@
const tailwindcss = require('tailwindcss') const tailwindcss = require("tailwindcss");
module.exports = { module.exports = {
plugins: [ plugins: [tailwindcss("./tailwind.config.js")],
tailwindcss('./tailwind.config.js') };
]
}

View file

@ -1,74 +1,84 @@
import React from "react"; import React from "react";
function ProgressBar(props: { function ProgressBar(props: {
done: number, done: number;
total: number, total: number;
units: string, units: string;
color: string, color: string;
}) { }) {
const { done, total, units, color } = props const { done, total, units, color } = props;
const width = Math.floor(done / total * 100) const width = Math.floor((done / total) * 100);
const rest = 100 - width const rest = 100 - width;
let [fg, bg] = [`bg-${color}-600`, `bg-${color}-100`] let [fg, bg] = [`bg-${color}-600`, `bg-${color}-100`];
if (color === 'white') { if (color === "white") {
[fg, bg] = ['bg-gray-600', 'bg-gray-100'] [fg, bg] = ["bg-gray-600", "bg-gray-100"];
} }
return ( return (
<div className={`relative ${bg} h-5`}> <div className={`relative ${bg} h-5`}>
<div className={`${fg} h-5 absolute top-0 left-0`} style={{width: `${width}%`}}></div> <div
<p className="absolute text-xs pl-1 pt-1">{done} of {total} {units}</p> className={`${fg} h-5 absolute top-0 left-0`}
style={{ width: `${width}%` }}
></div>
<p className="absolute text-xs pl-1 pt-1">
{done} of {total} {units}
</p>
</div> </div>
) );
} }
function Goal(props: { function Goal(props: {
subject: string, subject: string;
goal: string, goal: string;
done: number, done: number;
total: number, total: number;
units: string, units: string;
color: string, color: string;
}) { }) {
const { subject, goal, done, total, units, color } = props const { subject, goal, done, total, units, color } = props;
const width = "6em" const width = "6em";
const Tr = (props: { const Tr = (props: {
label: string, label: string;
value: string, value: string;
valueComponent?: React.ReactElement, valueComponent?: React.ReactElement;
}) => ( }) => (
<tr className="flex py-2"> <tr className="flex py-2">
<td className="text-gray-600" style={{width: width}}>{props.label}</td> <td className="text-gray-600" style={{ width: width }}>
{props.label}
</td>
<td className="flex-1"> <td className="flex-1">
{props.valueComponent ? props.valueComponent : props.value} {props.valueComponent ? props.valueComponent : props.value}
</td> </td>
</tr> </tr>
) );
return ( return (
<table className="w-full mb-10"> <table className="w-full mb-10">
<tbody> <tbody>
<Tr label="Subject" value={subject} /> <Tr label="Subject" value={subject} />
<Tr label="Goal" value={goal} /> <Tr label="Goal" value={goal} />
<Tr label="Progress" value={goal} valueComponent={ <Tr
<ProgressBar done={done} total={total} units={units} color={color} /> label="Progress"
}/> value={goal}
valueComponent={
<ProgressBar
done={done}
total={total}
units={units}
color={color}
/>
}
/>
</tbody> </tbody>
</table> </table>
) );
} }
function Copy(props: { function Copy(props: { children: React.ReactNode }) {
children: React.ReactNode return <p className="pb-4 leading-loose">{props.children}</p>;
}) {
return (
<p className="pb-4 leading-loose">
{props.children}
</p>
)
} }
function App() { function App() {
@ -78,7 +88,7 @@ function App() {
<h1 className="text-center pt-12 pb-6">Goals</h1> <h1 className="text-center pt-12 pb-6">Goals</h1>
<Copy> <Copy>
For me, a goal is something that is difficult for me to complete but For me, a goal is something that is difficult for me to complete but
easy for me to measure. I tend to add new goals as time progresses, easy for me to measure. I tend to add new goals as time progresses,
mistakenly assuming that I can support additional goals for free. To mistakenly assuming that I can support additional goals for free. To
counterbalance my tendancy to casually accumulate goals, I aim to only counterbalance my tendancy to casually accumulate goals, I aim to only
have three goals; I will not add a new goal until I complete an have three goals; I will not add a new goal until I complete an
@ -90,27 +100,33 @@ function App() {
</Copy> </Copy>
</section> </section>
<section className="pt-4"> <section className="pt-4">
<Goal subject="Meditation" <Goal
goal="Meditate for 10,000 hours" subject="Meditation"
done={100} goal="Meditate for 10,000 hours"
total={10000} done={100}
units="hrs" total={10000}
color="purple" /> units="hrs"
<Goal subject="Debt" color="purple"
goal="Pay my student debt balance" />
done={30000} <Goal
total={70000} subject="Debt"
units="USD" goal="Pay my student debt balance"
color="green" /> done={30000}
<Goal subject="Brazilian Jiu Jitsu" total={70000}
goal="Train until an instructor gives me a black belt" units="USD"
done={1} color="green"
total={5} />
units="belts" <Goal
color="white" /> subject="Brazilian Jiu Jitsu"
goal="Train until an instructor gives me a black belt"
done={1}
total={5}
units="belts"
color="white"
/>
</section> </section>
</div> </div>
) );
} }
export default App; export default App;

View file

@ -1,8 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<link rel="stylesheet" href="./index.css"> <link rel="stylesheet" href="./index.css" />
</head> </head>
<body> <body>
<div id="mount"></div> <div id="mount"></div>

View file

@ -4,4 +4,4 @@ module.exports = {
}, },
variants: {}, variants: {},
plugins: [], plugins: [],
} };

View file

@ -1,11 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es5",
"lib": [ "lib": ["dom", "dom.iterable", "esnext"],
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": true,
@ -19,7 +15,5 @@
"noEmit": true, "noEmit": true,
"jsx": "react" "jsx": "react"
}, },
"include": [ "include": ["src/**/*"]
"src/**/*"
]
} }

View file

@ -4287,6 +4287,11 @@ prelude-ls@~1.1.2:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
prettier@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.2.tgz#1ba8f3eb92231e769b7fcd7cb73ae1b6b74ade08"
integrity sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg==
pretty-hrtime@^1.0.3: pretty-hrtime@^1.0.3:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"

View file

@ -4,7 +4,10 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Learn to code" /> <meta name="description" content="Learn to code" />
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet"> <link
href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css"
rel="stylesheet"
/>
<title>Learn to code</title> <title>Learn to code</title>
</head> </head>
<body class="font-serif container max-w-2xl mx-auto px-8"> <body class="font-serif container max-w-2xl mx-auto px-8">
@ -42,33 +45,58 @@
<h2 class="text-3xl">Pricing</h2> <h2 class="text-3xl">Pricing</h2>
<p class="leading-relaxed mb-4"> <p class="leading-relaxed mb-4">
I charge <bold class="font-bold">£50</bold> per hour for video lessons I charge <bold class="font-bold">£50</bold> per hour for video lessons
and <bold class="font-bold">£100</bold> per hour for in-person and <bold class="font-bold">£100</bold> per hour for in-person sessions.
sessions. I am currently based in South Kensington, London. I am currently based in South Kensington, London.
</p> </p>
</div> </div>
<div class="my-4"> <div class="my-4">
<h2 class="text-3xl">Contact</h2> <h2 class="text-3xl">Contact</h2>
<p class="leading-relaxed mb-4"> <p class="leading-relaxed mb-4">
Whether you want to sign-up or simply want to learn more, send me an Whether you want to sign-up or simply want to learn more, send me an
email at <a href="mailto:wpcarro@gmail.com" class="font-bold text-blue-600 hover:underline">wpcarro@gmail.com</a>. email at
<a
href="mailto:wpcarro@gmail.com"
class="font-bold text-blue-600 hover:underline"
>wpcarro@gmail.com</a
>.
</p> </p>
<p class="text-center my-8">Why delay? <em>Start today.</em></p> <p class="text-center my-8">Why delay? <em>Start today.</em></p>
</div> </div>
<footer class="mb-8 lg:flex"> <footer class="mb-8 lg:flex">
<a class="block py-2 lg:w-1/4 text-center hover:underline" href="https://blog.wpcarro.dev">Blog</a> <a
<a class="block py-2 lg:w-1/4 text-center hover:underline" href="https://linkedin.com/in/williampatrickcarroll">LinkedIn</a> class="block py-2 lg:w-1/4 text-center hover:underline"
<a class="block py-2 lg:w-1/4 text-center hover:underline" href="https://twitter.com/wpcarro">Twitter</a> href="https://blog.wpcarro.dev"
<a class="block py-2 lg:w-1/4 text-center hover:underline" href="https://github.com/wpcarro">Github</a> >Blog</a
>
<a
class="block py-2 lg:w-1/4 text-center hover:underline"
href="https://linkedin.com/in/williampatrickcarroll"
>LinkedIn</a
>
<a
class="block py-2 lg:w-1/4 text-center hover:underline"
href="https://twitter.com/wpcarro"
>Twitter</a
>
<a
class="block py-2 lg:w-1/4 text-center hover:underline"
href="https://github.com/wpcarro"
>Github</a
>
</footer> </footer>
<!-- Global site tag (gtag.js) - Google Analytics --> <!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-160226702-1"> <script
</script> async
src="https://www.googletagmanager.com/gtag/js?id=UA-160226702-1"
></script>
<script> <script>
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);} function gtag() {
gtag('js', new Date()); dataLayer.push(arguments);
}
gtag("js", new Date());
gtag('config', 'UA-160226702-1'); gtag("config", "UA-160226702-1");
</script> </script>
</body> </body>
</html> </html>

View file

@ -1,7 +1,5 @@
const tailwindcss = require('tailwindcss') const tailwindcss = require("tailwindcss");
module.exports = { module.exports = {
plugins: [ plugins: [tailwindcss("./tailwind.config.js")],
tailwindcss('./tailwind.config.js') };
]
}

View file

@ -8,7 +8,7 @@ import type { Book } from "./store";
const App: React.FC = () => { const App: React.FC = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { isLoading, books } = useTypedSelector(state => ({ const { isLoading, books } = useTypedSelector((state) => ({
isLoading: state.isLoading, isLoading: state.isLoading,
books: state.books, books: state.books,
})); }));
@ -16,7 +16,7 @@ const App: React.FC = () => {
useEffect(() => { useEffect(() => {
async function fetchData() { async function fetchData() {
const entries = await getClient().getEntries(); const entries = await getClient().getEntries();
const books = entries.items.map(x => x.fields) as Book[]; const books = entries.items.map((x) => x.fields) as Book[];
dispatch(actions.setBooks(books)); dispatch(actions.setBooks(books));
} }
@ -30,9 +30,12 @@ const App: React.FC = () => {
<div className="container mx-auto"> <div className="container mx-auto">
<h1 className="py-6 text-2xl">Books</h1> <h1 className="py-6 text-2xl">Books</h1>
<ul> <ul>
{books.map(book => ( {books.map((book) => (
<li key={book.title} className="py-3"> <li key={book.title} className="py-3">
<p><span className="font-bold pr-3">{book.title}</span><span className="text-gray-600">{book.author}</span></p> <p>
<span className="font-bold pr-3">{book.title}</span>
<span className="text-gray-600">{book.author}</span>
</p>
</li> </li>
))} ))}
</ul> </ul>

View file

@ -8,10 +8,10 @@ let client: ContentfulClientApi;
// Idempotent way to get a reference to the Contentful client. // Idempotent way to get a reference to the Contentful client.
export const getClient = (): ContentfulClientApi => { export const getClient = (): ContentfulClientApi => {
if (typeof client !== 'undefined') { if (typeof client !== "undefined") {
return client; return client;
} else { } else {
if (typeof space === 'string' && typeof accessToken === 'string') { if (typeof space === "string" && typeof accessToken === "string") {
let client = createClient({ let client = createClient({
space, space,
accessToken, accessToken,
@ -19,7 +19,9 @@ export const getClient = (): ContentfulClientApi => {
return client; return client;
} else { } else {
throw new Error('Please set CONTENTFUL_SPACE_ID and CONTENTFUL_ACCESS_TOKEN'); throw new Error(
"Please set CONTENTFUL_SPACE_ID and CONTENTFUL_ACCESS_TOKEN"
);
} }
} }
} };

View file

@ -1,8 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<link rel="stylesheet" href="./index.css"> <link rel="stylesheet" href="./index.css" />
</head> </head>
<body> <body>
<div id="mount"></div> <div id="mount"></div>

View file

@ -22,9 +22,9 @@ export const { actions, reducer } = createSlice({
name: "application", name: "application",
initialState, initialState,
reducers: { reducers: {
toggleIsLoading: state => ({ ...state, isLoading: !state.isLoading }), toggleIsLoading: (state) => ({ ...state, isLoading: !state.isLoading }),
setBooks: (state, action) => ({ ... state, books: action.payload }), setBooks: (state, action) => ({ ...state, books: action.payload }),
} },
}); });
/** /**

View file

@ -4,4 +4,4 @@ module.exports = {
}, },
variants: {}, variants: {},
plugins: [], plugins: [],
} };

View file

@ -1,11 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es5",
"lib": [ "lib": ["dom", "dom.iterable", "esnext"],
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": true,
@ -19,7 +15,5 @@
"noEmit": true, "noEmit": true,
"jsx": "react" "jsx": "react"
}, },
"include": [ "include": ["src/**/*"]
"src/**/*"
]
} }

View file

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>sandbox.wpcarro.dev</title> <title>sandbox.wpcarro.dev</title>
</head> </head>
<body> <body>